M1 MACでpybind11を導入する

# 目的
pybind11はc++のコードを簡単にpythonから呼び出せ非常に便利ですが、あまりリファレンスが多くないのと、実行環境によって導入方法が結構異なるので
m1 macでの導入方法をメモ。

# pybind11
pybind11は、主に既存のC ++コードのPythonバインディングを作成するために、PythonでC++型を公開する軽量のヘッダー専用ライブラリです。
[公式ドキュメント](https://pybind11.readthedocs.io/en/stable/index.html)

cythonとかboost pythonとかあるけどpybind11が一番楽でオーバーヘッドも少ない印象。

# 実行環境
M1 MacBook Air 
```
ProductName:	macOS
ProductVersion:	12.3.1
BuildVersion:	21E258
```

# インストール

M1 macはまだまだcondaが使いづらいので今回はpipenvを使用します。
pipenvだとpythonのバージョンがらpipライブラリのバージョン、依存関係まで管理できます。
pipenvの情報は[こちら](https://pipenv.pypa.io/en/latest/)

Macなのでbrewを使います
```
brew install pipenv
```

pipenvのパッケージを作業ディレクトリ直下に保管したいので以下のコマンドを実行
```
export PIPENV_VENV_IN_PROJECT=1
```

pythonのバージョンを指定します
```
pipenv --python 3.9.9
```

pipenvで仮想環境構築
```
pipenv install
```

これで作業ディレクトリに`.venv`ディレクトリが作成され、中にパッケージが格納されます。
また、Pipfileというテキストファイルも作成され、ここにバージョンの情報が保存されます。

pybind11のインストール

```bash
pipenv install pybind11
```

この時点でPipfileは以下のようになっています


```
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
pybind11 = "*"

[dev-packages]

[requires]
python_version = "3.9"
```

## pybind11のinclude path
pybind11はheader-only library であるため、headerのincludeパスさえわかっていれば使用できます。
確認方法は
```
python3 -m pybind11 --includes
```
です。おそらくhomebrewは以下のパスもしくは.venvは以下のincludeのパスが表示されていると思います。


# c++モジュール作成

何はともあれinstallが環境したらcppのコードを書きます。公式ドキュメントから簡単な和算の関数を作成します
ちなみにここでvscodeで作業する場合は`.vscode/c_cpp_properties.json`内に先ほどの
`python3 -m pybind11 --includes`の結果のパスを入れることでvscodeがpybind11を認識してくれます


```example.cpp
#include <pybind11/pybind11.h>

int add(int i, int j) {
    return i + j;
}

PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 example plugin"; // optional module docstring

    m.def("add", &add, "A function that adds two numbers");
}
```
#include <pybind11/pybind11.h>でpybind11を呼び出して、add関数を作成した後、

```cpp
PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 example plugin"; // optional module docstring

    m.def("add", &add, "A function that adds two numbers");
}
```
でpythonで呼び出せる形式を整えています。
ここで`m.def("add", &add, "A function that adds two numbers");`についてpython側で呼び出す際の引数名やdocsを定義しています。
c++側の準備は以上となります。

# コンパイル & pythonから呼び出し

ここまでできたらあとはコンパイルしてpythonからimportできる形式のファおりに変換して、それをpythonからimportするだけです。
公式ドキュメントにMacOS用のコンパイルコマンドが乗っているのでそれをそのまま使用します。exampleのところは作成したcppファイル、作成するbuiltファイル名を好きにつけることができます。

```bash
c++ -O3 -Wall -shared -std=c++11 -undefined dynamic_lookup $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)
```

そうするとexample.cppのあったディレクトリに新しいファイルが作成されています
```
example.cpython-39-darwin.so
```

`.cpython-39-darwin.so`といいうのはpythonがc/c++のビルドされたファイルを読み込むための拡張子でosによって変わったりします。今回のコマンドでは`python3-config --extension-suffix`で取得した拡張子をそのまま使用しているので問題ないです。

ビルドに管んする情報は[ここ](https://pybind11.readthedocs.io/en/stable/compiling.html#building-manually)に色々記載があります。

作成した関数をpythonから呼び出してみます。
```bash
bash-3.2$ python
Python 3.9.9 (main, Nov 21 2021, 03:16:13) 
[Clang 13.0.0 (clang-1300.0.29.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.add(1, 2)
3
```

以上!c++のコードをpythonで使用することができました！

# その他エラー

# clang: error:

最初Linux用のbuildコマンドを使用したのでエラーが出てしまいました
```bash
# linux用のコマンド
c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)
```

`clang: error: linker command failed with exit code 1 (use -v to see invocation)`っていうg++でよくあるエラーが出てくるので要注意です。(このエラー調べてもまずpybind11には辿り着けません。)


## miniforge3
M1Macでcondaを使用するかたはminiforge3から使用する方も多いかもしれません。私の環境ではminiforge3からconda createで仮想化環境を構築してpybind11を導入したところ、ビルドはできたんですが、pythonでimportはできませんでした。(import error)
原因はついにわかりませんでしたが、
`.cpython-39-darwin.so`を読み込む他のライブラリ(つまりc/c++の力を借りているpythonライブラリ)はM1 Macでよくエラーを見かけるのでまだM1が対応していないのかもしれないですね