SwiftyTesseractとOpenCVを入れたら「duplicate symbol」しまくった話

タイトルの通り。`duplicate symbol`が出まくって面を食らったので解決策を備忘録として残しておきます。

# 環境情報

| 名前 | バージョン |
|---|---|
| Xcode | 12.3 (12C33) |
| Swift | 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28) |
| SwiftyTesseract | 4.0.0 |
| CMake | 3.19.3 |
| Python | 2.7.16 |
| xcode-select | 2384 |
| OpenCV | 4.5.1 |

# SwiftyTesseractとOpenCVを同じプロジェクトで使う

二つを同じプロジェクトで使うための手順を示します。

## SwiftyTesseractをプロジェクトに追加する

2021年1月19日現在、[SwiftyTesseractはSwift Package Managerからしか追加できません](https://github.com/SwiftyTesseract/SwiftyTesseract#Installation )ので、Swift Package Managerから追加します。

Xcode上で「File > Swift Packages > Add Package Dependency...」と選択して、![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609272/c8c987b2-e956-cf9a-e29b-fd4a587370cb.png)
入力欄に以下のGitHubのURLを入力します。

```
https://github.com/SwiftyTesseract/SwiftyTesseract
```

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609272/9361e33b-9416-4faf-a594-9f185cda7c1a.png)

## OpenCVをプロジェクトに追加する

### iOS用にビルドする

[GitHubからOpenCVを入手](https://github.com/opencv/opencv )して、`platform/ios/`にある以下のコマンドでiOS用にビルドします。

```bash
$ ./platform/ios/build_framework.py <output_dir>
```

### ビルドしたframeworkファイルをプロジェクトに追加する

プロジェクトファイルを選んで、左ペインの「TARGETS」からアプリを選択します。
そして「Build Phases > Link Binary With Libraries (_n_ items)」と選択して、ビルドで出力された「opencv2.framework」をドラッグ&ドロップなどで追加します。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609272/ce16a3b2-56b8-efe7-d981-d55a55d87b77.png)

### Other Linker Flagsに`-all_load`を追加する

プロジェクトファイルを選んで、左ペインの「PROJECT」からアプリを選択します。
「Build Settings」を選択して、「Linking > Other Linker Flags」に`-all_load`を追加します。
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609272/5d5bcd20-714a-2f0f-e399-4a231e981a14.png)


## libc++.tbdをプロジェクトに追加する

同じくプロジェクトファイルの「TARGETS」からアプリを選択します。
そして「General > Frameworks, Libraries, and Embedded Content」と選択して、左下の「+」ボタンを押します。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609272/972863c5-3099-4b88-82e0-c733bf0e3fd3.png)
検索窓に「libc++」と入力すると「libc++.tbd」が出てくると思うのでそれを追加します。
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609272/d819a464-f04f-c115-b0a7-ebc012311d1f.png)

## 試しにビルドしてみるが…

試しにビルドしてもこの時点ではうまくいきません。エラーを見ると「509 duplicate symbols for architecture x86_64(シミュレータなら×86_64、実機ならarm64)」というものすごい数の競合が起きていることがわかります。
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609272/70573526-6a2d-7f7d-86f7-153c7116ff4d.png)
解決方法ですが、__opencv2.frameworkから競合が起きているファイルを削除する__ことで解消できます。

## 競合が起きているファイル(.oファイル)をまとめる

エラーを頼りに競合が起きてるファイルをまとめます。執筆者の環境では以下のファイル内で競合が起きていました。

```text
jutils.o pngrio.o pngwio.o jdcoefct.o jaricom.o jdmainct.o jdcolor.o pngerror.o jfdctint.o pngget.o pngrutil.o jmemmgr.o jccoefct.o jcmaster.o jidctflt.o pngpread.o jcprepct.o jerror.o pngset.o jctrans.o jcparam.o jdinput.o pngwtran.o jdarith.o jdsample.o jcomapi.o png.o pngrtran.o jchuff.o jddctmgr.o jidctfst.o jfdctfst.o pngwrite.o jfdctflt.o jdatasrc.o jdapistd.o pngmem.o jdapimin.o pngwutil.o jcdctmgr.o jdtrans.o jdhuff.o jdmaster.o jcapistd.o jdatadst.o jquant2.o jdmerge.o jmemnobs.o jccolor.o jdmarker.o jdpostct.o jcmarker.o jidctred.o jcarith.o jcmainct.o pngtrans.o jcinit.o jcsample.o jquant1.o pngread.o jidctint.o jcapimin.o
```

上記リストは、後でコマンドに引数として食わせるときに使いやすいようテキストファイルなどに書いておきます。

```bash
$ echo "jutils.o pngrio.o ..." > duplicate_lib_list
```

## OpenCVをアーキテクチャごとに分解する

lipoコマンドを使って、フレームワークフォルダ内のopencv2をアーキテクチャごとに分解します。

まず何が含まれているか以下コマンドで確認して、

```bash
$ cd opencv2.framework
$ lipo -info opencv2
Architectures in the fat file: opencv2 are: armv7 armv7s i386 x86_64 arm64
```

以下コマンドでバラします。

```bash
$ lipo -thin armv7 opencv2 -output opencv2_armv7
$ lipo -thin armv7s opencv2 -output opencv2_armv7s
$ lipo -thin i386 opencv2 -output opencv2_i386
$ lipo -thin x86_64 opencv2 -output opencv2_x86_64
$ lipo -thin arm64 opencv2 -output opencv2_arm64
```

## 競合が起きているファイルを削除する

バラしたファイルに対して以下コマンドを実行して、競合が起きているファイルをそれぞれから削除します。

```bash
$ cat duplicate_lib_list | xargs ar -dv opencv2_armv7
$ cat duplicate_lib_list | xargs ar -dv opencv2_armv7s
$ cat duplicate_lib_list | xargs ar -dv opencv2_i386
$ cat duplicate_lib_list | xargs ar -dv opencv2_x86_64
$ cat duplicate_lib_list | xargs ar -dv opencv2_arm64
```

## バラしたファイルから再生成する

以下コマンドを実行してopencv2を再生成しましょう。

```bash
$ lipo -create opencv2_arm64 opencv2_armv7 opencv2_armv7s opencv2_i386 opencv2_x86_64 -output opencv2
```

あとは途中作ったテキストファイルなどの余計なファイルを削除してリビルドしましょう。
競合が解消されてビルドできるようになっているはずです！

# 参考記事

1. [OpenCV が Objective-C / Swift で使えるようになります & 使ってみた - Qiita](https://qiita.com/treastrain/items/0090d1103033b20de054 )
2. [Xcodeでビルドした時の”duplicate symbols”エラーを回避する](https://qiita.com/conta_/items/af7c88f2d16d97985387 )