想定している環境
- Mac
- PyCall はインストール済み
- pyenv で Python をインストールしていて、PyCall からはこの Python を利用したい
遭遇した問題
こういう Ruby スクリプト1を書いて、
require 'pycall/import'
puts "ok"
実行したら、
$ ruby test.rb
ImportError: No module named site
というエラーが出る。
解決方法
pyenv で Python をインストールするときに、共有ライブラリを作るようにオプションを指定する。
$ CONFIGURE_OPTS="--enable-shared" pyenv install 3.6.1
こうすると、~/.pyenv/versions/3.6.1
配下に libpython3.6m.dylib が生成される。2
追跡
site モジュールを探す
$ cd $(pyenv prefix)
$ find . -name site.py
./lib/python3.6/site-packages/jedi/evaluate/site.py
./lib/python3.6/site.py
site.py はある。
ということは、単にパスが通っていないのではないかと推測。
PYTHONPATH を通してみる
PYTHONPATH=$HOME/.pyenv/versions/3.6.1/lib/python3.6 ruby test.rb
File "/Users/cesare/.pyenv/versions/3.6.1/lib/python3.6/site.py", line 177
file=sys.stderr)
^
SyntaxError: invalid syntax
モジュールは無事に見つかるようになったが、今度はシンタックスエラーが出た。
どうやら、このモジュールをロードした処理系が Python3.x 系のものではないのでは疑惑。
PyCall のソースを軽く眺めた感じ、FFI を使っているようなので、たぶん処理系は共有ライブラリではないかと予想した。
処理系の共有ライブラリを探す
Mac だと共有ライブラリの拡張子は .dylib であることが多いので、ひとまずこんな感じで探してみる。
$ find . -name '*.dylib'
./lib/python3.6/site-packages/matplotlib/.dylibs/libpng16.16.dylib
./lib/python3.6/site-packages/matplotlib/.dylibs/libz.1.2.10.dylib
./lib/python3.6/site-packages/scipy/.dylibs/libgcc_s.1.dylib
./lib/python3.6/site-packages/scipy/.dylibs/libgfortran.3.dylib
./lib/python3.6/site-packages/scipy/.dylibs/libquadmath.0.dylib
幾つか出てきたが、これらは後からインストールしたパッケージのものなので違う。
.pyenv/versions/3.6.1/lib
配下を覗いてみると libpython3.6m.a というファイルなら転がっているが、 .a は共有ライブラリではないし、探し物はこれではない。おそらく libpython3.6m.dylib というファイルが存在していないといけないのでは、と推測。
pyenv で処理系の共有ライブラリが作られるようにする
Python の処理系のソースを取ってきて configure
のオプションを調べると --enable-shared
というものが指定できるらしいことが分かった。pyenv のインストール時に configure
のオプションを指定するには、環境変数 CONFIGURE_OPTS
を使えば良いらしい。
ということで、
$ CONFIGURE_OPTS="--enable-shared" pyenv install 3.6.1
こうすれば libpython3.6m.dylib が生成された。
テスト用の Ruby スクリプトを再実行してみると、
$ ruby test.rb
ok
無事動いた。
補足
libpython の検索順序
PyCall が libpython を探す様子は、環境変数 DEBUG_FIND_LIBPYTHON
を使うと観測できる。
$ DEBUG_FIND_LIBPYTHON=1 ruby -e 'require "pycall"'
というようにすれば、標準出力に検索したパスが列挙されるようになる。