Boost.pythonをhomebrewで導入


[Boost.python](http://www.boost.org/doc/libs/1_60_0/libs/python/doc/html/index.html)を試してみようとhomebrewで導入してみたところ、少々ハマりましたのでメモっておきます。

# 環境

- OS X El Capitan(10.11.3)
- Python 2.7.11 (homebrew)
- boost 1.59 (homebrew)
- boost-python 1.59 (homebrew)

# 使用例(C++)

c++の自乗を返す関数をpythonから呼んでみます。

```hello_ext.cpp
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>

int square(int a)
{
    return a*a;
}

BOOST_PYTHON_MODULE(hello_ext)
{
	using namespace boost::python;
	def("square",square);
}
```

hello_ext.soをビルドするためのMakefileです。

```Makefile
all: hello_ext.so

CC = g++

FRAMEWORK_PATH=/usr/local/opt/python/Frameworks
INCLUDE_PATH=$(FRAMEWORK_PATH)/Python.framework/Versions/2.7/include/python2.7

hello_ext.o: hello_ext.cpp
    $(CC) -Wall -O2 -fPIC -c $< -I$(INCLUDE_PATH)

hello_ext.so: hello_ext.o
    $(CC) -shared  -o $@ $< -lboost_python -F$(FRAMEWORK_PATH) -framework Python
```

pythonの呼び出し側です。

```hello.py
#!/usr/bin/env python
import hello_ext
print hello_ext.square(2)
```

ビルドと実行結果です。

```結果
$ make
g++ -shared  -o hello_ext.so -I/usr/local/opt/python/Frameworks/Python.framework/Versions/2.7/include/python2.7 hello_ext.o -lboost_python -F/usr/local/opt/python/Frameworks -framework Python
$ python hello.py
4
```

なるほど、昔ながらのPython拡張に比べてコードがすっきりしてよいですね。

# 使用例(Objective-C++)

既存のフレームワークを使いたいなどの理由で、Objective-Cを混在させるにはObjective-C++を使うといけますね。下記をhello_ext.mmとして作成します。

```objc++
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>

#import <Foundation/Foundation.h>

int square(int a)
{
    return a*a;
}

void nslog(void)
{
    NSLog(@"Hello");
}

BOOST_PYTHON_MODULE(hello_ext)
{
    using namespace boost::python;
    def("square",square);
    def("nslog",nslog);
}
```

Makefileは下記です。コンパイル時には-x objective-c++をつけ、リンク時にはフレームワークのパスとフレームワーク名をつけます。

```
all: hello_ext.so

CC =clang++

FRAMEWORK_PATH=/usr/local/opt/python/Frameworks
INCLUDE_PATH=$(FRAMEWORK_PATH)/Python.framework/Versions/2.7/include/python2.7
FRAMEWORK_PATH2=/System/Library/Frameworks

STD_CPP_PATH = /usr/include/c++/4.2.1/

FRAMEWORK_OPTIONS=-F$(FRAMEWORK_PATH) -framework Python -F$(FRAMEWORK_PATH2) -framework Foundation

hello_ext.o: hello_ext.mm
	$(CC) -x objective-c++ -Wall -O2 -fPIC -c $< -I$(INCLUDE_PATH) -I$(STD_CPP_PATH)

hello_ext.so: hello_ext.o
	$(CC) -shared -o $@  $< -lboost_python $(FRAMEWORK_OPTIONS)

.PHONY: clean
clean:
	rm *.so *.o
```

下記で呼び出せます。

```py
import hello_ext

hello_ext.nslog()
print hello_ext.square(2)
```
実行結果は下記です。
```
2017-04-09 11:45:29.818 Python[7710:333116] Hello
4
```

# ハマった点と解決までの経緯

まず[公式チュートリアル](http://www.boost.org/doc/libs/1_60_0/libs/python/doc/html/tutorial/tutorial/hello.html)はbjamを使えと書いてあります。bjamはhomebrewの boost-buildを入れるとインストールできますが、Jamfileとやらのパスを指定せよといわれ、homebrewでのインストール先がわからず断念しました。

次に[Boost.Python の機能をざっと紹介してみる](http://d.hatena.ne.jp/moriyoshi/20091214/1260779899)などの先人に習ってg++でビルドをしてみましたが、引数の受け渡しをしようとした時点で、

```
Segmentation fault: 11
```

だの、

```
Fatal Python error: PyEval_SaveThread: NULL tstate
```

だのいわれて散々でした。

後者のエラーメッセージが検索に引っ掛かり、[StackOverFlowの記事](http://stackoverflow.com/questions/4985933/is-the-pythreadstate-of-the-main-python-thread-expected-to-be-null)にたどり着きました。

> だいたいこういうのは複数のバージョンの python が混在してるときじゃね？
> otool -Lとかで依存関係を見てみたら;)

とのコメントがまさにビンゴ。確かにOSXネイティブなパスとhomebrewのパスが混在していました。

ということで、-F オプションをつけてフレームワークのパスを OSX ネイティブのものではなく homebrew のものに置き換えることで解決しました。

-Fオプションには man ld でたどり着きました。恥ずかしながら -framework オプションも理解しておらず。勉強不足でしたがどうにかたどり着きました。


# 参考リンク

- http://d.hatena.ne.jp/moriyoshi/20091214/1260779899
- http://www.boost.org/doc/libs/1_60_0/libs/python/doc/html/tutorial/tutorial/hello.html
- http://alpha.osdn.jp/devel/boost.python_ja.pdf
- http://d.hatena.ne.jp/saket/20111017/1318841837
- http://stackoverflow.com/questions/4985933/is-the-pythreadstate-of-the-main-python-thread-expected-to-be-null
- http://stackoverflow.com/questions/1780133/linking-against-apple-frameworks-with-gcc