pkgConfig を書いて NDI SDK（fat static library） をパッケージとしてアプリに組み込む

> Network Device Interface (NDI) is a high performance standard that allows anyone to use real time, ultra low latency video on existing IP video networks.

[NDI](https://ndi.tv/about-ndi/) は IP ネットワーク上でビデオやオーディオを効率よく伝達する方式のひとつです。

iOS 向けの [SDK](https://ndi.tv/sdk/#download) も公開されていてダウンロードすることができます。ライブラリはパス `/Library/NDI SDK for Apple/lib/` にプラットフォームごとにインストールされますが、実体は fat な static library です。

```
$ file /Library/NDI\ SDK\ for\ Apple/lib/iOS/libndi_ios.a 
/Library/NDI SDK for Apple/lib/iOS/libndi_ios.a: Mach-O universal binary with 4 architectures: [arm_v7:current ar archivecurrent ar archive] [i386] [x86_64] [arm64]
/Library/NDI SDK for Apple/lib/iOS/libndi_ios.a (for architecture armv7):	current ar archive
/Library/NDI SDK for Apple/lib/iOS/libndi_ios.a (for architecture i386):	current ar archive
/Library/NDI SDK for Apple/lib/iOS/libndi_ios.a (for architecture x86_64):	current ar archive
/Library/NDI SDK for Apple/lib/iOS/libndi_ios.a (for architecture arm64):	current ar archive
```

これを Swift Package から利用することを考えます。そのために少し作業が必要になったのですが、pkgConfig に情報を記述する方法が参考になったのでここにまとめておきます。

## 参考になった記事

[こちら](https://stackoverflow.com/questions/70557329/link-to-fat-static-library-inside-swift-package)の stackoverflow の記事が大変参考になりました。そこには3通りのリンクの方法が記載されています。

1. XCFramework を生成する方法
1. module.modulemap を記述して system library を定義し、Build Settings の Library Search Paths にパスを指定する方法
1. module.modulemap を記述して system library を定義し、pkgConfig でライブラリの情報を提供する方法 

3 種類の方法をそれぞれ試しました。XCFramework を生成する方法 は stackoverflow では GitHub の公開設定を理由にして最終的な回答にはされていませんが、試したところ `No such module` エラーが解消できずに上手くいかなかったため、2. と 3. の方法のみをまとめています。

## パッケージとしてアプリに組み込む手順

ここで解説している構成はプロジェクトにまとめて下記のリポジトリにあげています。NDI SDK は別途インストールしてください。

https://github.com/naru-jpn/NDISwiftPackage

### pkgConfig を使わない方法

まずは準備として pkgConfig を使わない方法を解説します。プロジェクトの構成は以下のようになっています。

```
- NDIApp.xcodeproj
- NDIApp（アプリ本体）
- Packages（ローカルパッケージ）
  - Package.swift
  - Sources
    - NDI（NDIのラッパーライブラリ）
      - NDI.swift 
    - libndi（システムライブラリ）
      - module.modulemap
- broadcast-upload（App Extension）
```

システムライブラリとして `libndi` を定義し、そのラッパーとして `NDI` を定義しています。アプリ本体からインポートするのは `NDI` のみです。`Package.swift` は以下のような記述になっています。

```swift
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

let package = Package(
    name: "NDI",
    products: [
        .library(name: "NDI", targets: ["NDI"]),
    ],
    dependencies: [
    ],
    targets: [
        .target(
            name: "NDI",
            dependencies: ["libndi"],
            linkerSettings: [
                .linkedFramework("Accelerate"),
                .linkedFramework("VideoToolbox"),
                .linkedLibrary("c++")
            ]
        ),
        .systemLibrary(name: "libndi")
    ]
)
```

`NDI` は `libndi` に依存しています。`libndi` ディレクトリ内にある `module.modulemap` は以下のように記述しています。

```
module libndi {
    umbrella header "/Library/NDI SDK for Apple/include/Processing.NDI.Lib.h"
    link "ndi_ios"
    export *
}
```

umbrella header として、mac 上にインストールされた NDI SDK のヘッダーファイルのパスを指定しています。NDI SDK の実体がある場所を教える必要があるので、プロジェクトファイルの Build Settings → Library Search Paths に `"/Library/NDI SDK for Apple/lib/iOS"` を追加します。

上記までの作業で NDI パッケージ内で NDI SDK の機能を利用できるようになります。プロジェクトファイルに設定値を書き込まずに、pkgConfig を記述する方法もあります。

### pkgConfig を使う方法

pkgConfig を使用する場合、Library Search Paths にパスの指定が必要なくなるのでプロジェクトファイルから値を削除しておきます。

pkgConfig ファイルは [4つのパス](https://github.com/apple/swift-package-manager/blob/84a5bc77681553419b25cc943c68c00d87d5f8db/Sources/Utility/PkgConfig.swift#L50-L53) かもしくは[環境変数 `PKG_CONFIG_PATH`](https://github.com/apple/swift-package-manager/blob/84a5bc77681553419b25cc943c68c00d87d5f8db/Sources/Utility/PkgConfig.swift#L103-L108) で指定されたパスから検索されるようです。僕は `/usr/local/lib/pkgconfig/libndi_ios.pc` に設置しました。

```
NDI_SDK_ROOT=/Library/NDI\ SDK\ for\ Apple

Name: NDI SDK for iOS
Description: The NDI SDK for iOS
Version: 5.1.1
Cflags: -I${NDI_SDK_ROOT}/include
Libs: -L${NDI_SDK_ROOT}/lib/iOS -lndi_ios
```

pkgConfig ファイルを設置したら、`Package.swift` の `libndi` に pkgConfig のファイル名を指定します。

```
.systemLibrary(name: "libndi")
↓
.systemLibrary(name: "libndi", pkgConfig: "libndi_ios")
```

これでアプリのプロジェクトファイルに手を加えずに、 NDI をパッケージ上でリンクして使えるようになりました🙌

```swift
import NDI

...
try NDISender.shared.send(pixelBuffer: pixelBuffer)
```


<kbd><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112629/cf25dc12-1883-b33f-6f93-cbab9c54a43b.gif" width="350"></kbd>

## 参考

- [Link to fat Static Library inside Swift Package](https://stackoverflow.com/questions/70557329/link-to-fat-static-library-inside-swift-package) 
- [Cleanly handling /usr/local/ with Swift package manager and libevent](https://stackoverflow.com/questions/47096503/cleanly-handling-usr-local-with-swift-package-manager-and-libevent)