PySide(Qt4)とPyQt(Qt5)に必要な環境変数をセットする


Qt4/Qt5およびPySide(Qt4対応,Qt5に対応していないようなので)/PyQt(Qt5対応)を両立する際、環境変数まわりで少し工夫が必要だった。

## 前提環境

- OS X(10.9.5)
- virtualenvwrapper
- Homebrew

## Qt4/Qt5のインストール

Homebrew経由でインストールする。

```bash:Qt4/Qt5のインストール(Homebrew)
$ brew install qt
$ brew install qt5
```

## PySideのインストール

PySideはpip経由でインストール可能だったため、virtualenv下にインストールする。

```bash:PySideのインストール(pip)
$ mkvirtualenv -p python3 pyside
(pyside)$ mkvirtualenv 
(pyside)$ pip install PySide
```

## PyQtのインストール

PyQtも同様にpip経由でインストールを試みたところ、既に削除されたのかインストールできなかった。

```bash:PyQtのインストール(pipで失敗)
$ mkvirtualenv -p python3 pyqt
(pyqt)$ pip install PyQt5
Downloading/unpacking PyQt5
  Could not find any downloads that satisfy the requirement PyQt5
Cleaning up...
No distributions at all found for PyQt5
Storing debug log for failure in /Users/FGtatsuro/.pip/pip.log
```

そのため、PyQtはHomebrew経由でインストールすることとした。インストール時のトラブルは[こちら](http://qiita.com/FGtatsuro/items/c96b57e1c95edf34abfe)。なおPyQt自体はHomebrew経由でインストールしたが、関連するライブラリをインストールすることを考えて、PySideと同様にvirtualenv環境は作っておく。

```bash:PyQtのインストール(Homebrew)
(pyqt)$ brew install pyqt5
```

## PySideの実行

[ここ](http://qt-project.org/wiki/Hello-World-in-PySide)にあるPySideのサンプルを動かそうとしたが、動的ライブラリの読み込みに失敗し実行できない。

```bash:PySideの実行(失敗)
(pyside)$ python pyside_test.py
Traceback (most recent call last):
  File "pyside_test.py", line 6, in <module>
    from PySide.QtCore import *
ImportError: dlopen(/Users/FGtatsuro/.homesick/repos/dotfiles/home/.virtualenvs/pyside/lib/python3.4/site-packages/PySide/QtCore.so, 2): Library not loaded: libpyside.cpython-34m.1.2.dylib
  Referenced from: /Users/FGtatsuro/.homesick/repos/dotfiles/home/.virtualenvs/pyside/lib/python3.4/site-packages/PySide/QtCore.so
  Reason: image not found
```

読み込もうとしている動的ライブラリはvirtualenvのsite-package下にある。そのパスを環境変数`DYLD_LIBRARY_PATH`に追加することで実行できる。

```bash:PySideの実行(成功)
(pyside)$ ls /Users/FGtatsuro/.homesick/repos/dotfiles/home/.virtualenvs/pyside/lib/python3.4/site-packages/PySide/libpyside.cpython-34m.1.2.dylib
/Users/FGtatsuro/.homesick/repos/dotfiles/home/.virtualenvs/pyside/lib/python3.4/site-packages/PySide/libpyside.cpython-34m.1.2.dylib
(pyside)$ export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/Users/FGtatsuro/.homesick/repos/dotfiles/home/.virtualenvs/pyside/lib/python3.4/site-packages/PySide
(pyside)$ python pyside_test.py
```

## PyQtの実行

[ここ](http://www.thehackeruniversity.com/2014/01/23/pyqt5-beginner-tutorial/)にあるPyQtのサンプルを動かそうとしたが、こちらはPyQtのモジュールの読み込みに失敗する。

```bash:PyQtの実行(失敗)
(pyqt)$ python pyqt_test.py
Traceback (most recent call last):
  File "pyqt_test.py", line 4, in <module>
    from PyQt5.QtCore import *
ImportError: No module named 'PyQt5'
```

Homebrew経由でインストールしたPyQt(+PyQtが依存しているsip)のsite-packageが、virtualenv環境から見えていないのが原因。そのパスを環境変数`PYTHONPATH`に追加することで実行できる。

```bash:PyQtの実行(成功)
(pyqt)$ export PYTHONPATH=$PYTHONPATH:/usr/local/opt/pyqt5/lib/python3.4/site-packages:/usr/local/opt/sip/lib/python3.4/site-packages
(pyqt)$ python pyqt_test.py
```

## 環境変数の切り替え

ここまででPySide/PyQtの実行はできたが、これらを共存するとなるとまだ問題がある。PySideに必要な`DYLD_LIBRARY_PATH`とPyQtの実行に必要な`PYTHONPATH`が両方存在した場合、PyQtの実行に失敗する。(ライブラリの読み込みトラブル?)

```bash:環境変数によるPyQt実行の失敗
(pyqt)$ env | egrep 'DYLD_LIBRARY_PATH|PYTHONPATH'
DYLD_LIBRARY_PATH=/Users/FGtatsuro/.homesick/repos/dotfiles/home/.virtualenvs/pyside/lib/python3.4/site-packages/PySide
PYTHONPATH=/usr/local/opt/pyqt5/lib/python3.4/site-packages:/usr/local/opt/sip/lib/python3.4/site-packages

(pyqt)$ python pyqt_test.py
Traceback (most recent call last):
  File "pyqt_test.py", line 5, in <module>
    from PyQt5.QtWidgets import *
RuntimeError: the PyQt5.QtCore module failed to register with the sip module
```

`DYLD_LIBRARY_PATH`をunsetすれば実行可能になるが、virtualenv環境を切りかえるたびにこの変数を意識しなくてはならないのは非常に面倒に感じる。

そこでPySide/PyQtのvirtualenv環境をactivate/deactivateした際に必要な環境変数をexport/unsetするようにvirtualenvwrapperの設定ファイルを変更した。`.virtualenvs/postactivate`はactivate「後」に実行され、`.virtualenvs/predeactivte`はdeactive「前」に実行される。PySide環境の判定が「virtualenv環境下の」pipに依存しており、判定のタイミングはvirtualenv環境に入った「後」とvirtualenv環境から出る「前」でなくてはならない。

判定の条件は次の通り。

- PySide: pipでPySideがインストールされている。
- PyQt: virtualenv環境名に文字列`pyqt`を含む。

```bash:.virtualenvs/postactivate
#!/bin/bash
# This hook is run after every virtualenv is activated.

# PySideがpipでインストールされていれば、DYLD_LIBRARY_PATHに必要なパスを追加する。
if [ `pip freeze | grep PySide` ]; then
  # Now, I can use PySide on only 3.4.x environment
  VENV_PYSIDE_PATH=$VIRTUAL_ENV/lib/python3.4/site-packages/PySide
  if [ $DYLD_LIBRARY_PATH ]; then
    export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$VENV_PYSIDE_PATH
  else
    export DYLD_LIBRARY_PATH=$VENV_PYSIDE_PATH
  fi
fi

# virtualenv環境名が"pyqt"を含んでいれば、PYTHONPATHに必要なパスを追加する。
if [ `basename $VIRTUAL_ENV | grep pyqt` ]; then
  # Now, I can use PyQt on only 3.4.x environment
  BREW_PYQT_PATH=`brew --prefix pyqt5`/lib/python3.4/site-packages:`brew --prefix sip`/lib/python3.4/site-packages
  if [ $PYTHONPATH ]; then
    export PYTHONPATH=$PYTHONPATH:$BREW_PYQT_PATH
  else
    export PYTHONPATH=$BREW_PYQT_PATH
  fi
fi
```

```bash:.virtualenvs/predeactivate
#!/bin/bash
# This hook is run before every virtualenv is deactivated.

# PySideがpipでインストールされていれば、DYLD_LIBRARY_PATHから関連するパスを除く。
if [ `pip freeze | grep PySide` ]; then
  # Now, I can use PySide on only 3.4.x environment
  VENV_PYSIDE_PATH=$VIRTUAL_ENV/lib/python3.4/site-packages/PySide
  export DYLD_LIBRARY_PATH=`echo $DYLD_LIBRARY_PATH | sed -e "s|:*$VENV_PYSIDE_PATH||g"`
  if [ -z $DYLD_LIBRARY_PATH ]; then
    unset DYLD_LIBRARY_PATH
  fi
fi

# virtualenv環境名が"pyqt"を含んでいれば、PYTHONPATHから関連するパスを除く。
if [ `basename $VIRTUAL_ENV | grep pyqt` ]; then
  # Now, I can use PyQt on only 3.4.x environment
  BREW_PYQT_PATH=`brew --prefix pyqt5`/lib/python3.4/site-packages:`brew --prefix sip`/lib/python3.4/site-packages
  export PYTHONPATH=`echo $PYTHONPATH | sed -e "s|:*$BREW_PYQT_PATH||g"`
  if [ -z $PYTHONPATH ];then
    unset PYTHONPATH
  fi
fi
```

PyQtをpip経由でインストールすることができなかったため、判定の条件をvirtualenv環境の命名規則に頼らざるを得なかった。その点が少し残念なので、いいアイデアがないか考える。