Doge log

Abby CTO 雑賀 力王のオフィシャルサイトです

virtualenv

最近、流行りのvirtualenvですが、仕組み的なものにあまり触れられてないように
思えたので書いてみます。

機能

とりあえずvirtualenvの機能は

  • ライブラリがまっさらな状態な環境を作る
  • 作られた環境に対してのみモジュールをインストールできる

です。
これを実現する仕掛けはそれほど難しくありません。
(virtualenv自体も基本的に1ファイルのモジュールです。)
とりあえず簡単に見ていきます。

ライブラリパス

まっさらなライブラリを持った状態のpython環境はどうやって作るのでしょう?
pythonは自分自身のパスの上位のlib/pythonx.xをライブラリとして認識します。
pythonをソースからprefix指定でビルドした事があればわかると思います。)

.
|-- bin
|   `-- python
`-- lib
    `-- python2.5
        `-- ここが標準ライブラリとして読み込まれる

virtualenvはこの仕様を利用しています。
ではライブラリがまっさらな環境を自前で作ってみましょう。
適当にdirを作ってbin/pythonには/usr/bin/python2.5など使用したいpythonバイナ
リをコピーして置きます。
そしてlib/python<バージョン>のdirを作ります。
しかし、このままでは標準ライブラリのパスとして認識しないっぽいです。なので
os.pyをコピーして置いてやります。

.
|-- bin
|   `-- python
`-- lib
    `-- python2.5
        `-- os.py

これで最小限な環境は完成です。
bin/pythonを実行してライブラリパスが変わっているか確認してみて下さい。
よく分からない場合は

bin/python -c "import sys;print sys.path"

を実行するとライブラリを読み込むパスが変わっていると思います。

virtualenvでは環境を新たに作るとlib/pythonx.x以下に元々の標準ライブラリ群
のみをコピーします。
作った環境のlib/pythonx.x以下を見てみるとわかると思います。

サードパーティライブラリ

virtualenvではサードパーティライブラリを含めるかどうか選ぶ事ができます。
(--no-site-packagesで含めない)
通常サードパーティライブラリのパスをいじるにはsite.pyで変更します。
virtualenvも同じ仕組みです。
virtualenvは環境を作る際に独自のsite.pyを置きます。
このsite.pyではno-global-site-pakcages.txtがあるかないかで元のサードパーテ
ィライブラリのパスを含めるかどうか判断します。
これも作った環境のlib/pythonx.x以下のsite.pyを見てみるとわかると思います。

インストール先の変更

virtualenvを使うとインストール先が新しく構築した環境になります。
これはdistutilsの仕組みを利用しています。
virtualenvはsite.pyの時と同様にdistutilsを独自のものに置き換えます。
通常、distutils.sysconfig.get_python_lib()はサードパーティライブラリのイン
ストール先を返しますがvirtualenvではこの辺の関数をごっそり置き換えます。
関数で返るパスをコントロールすることでインストール先をうまく切り替えます。
これも作った環境のlib/pythonx.x/distutils/__init__.pyを見てみるとわかる
と思います。

activate

virtualenvでは構築した環境のbin/pythonを叩かない限りその環境でうまく動作し
ません。
(上記のライブラリのパスの問題があるから)
なので通常はactivate機能を使います。

source bin/activate

activateは単なるshellです。
(エディタで開くとわかります)
activateはシンプルな機能です。
通常のPATHの前に構築した環境のbinをセットしているだけです。
なのでpythonと叩いた場合に先に構築したbin/pythonというだけの話です。

bootstrap

virtualenvの真骨頂はこのbootstrap機能です。
bootstrapは

  • setuptoolsのインストール
  • virtualenv環境の構築
  • 事後処理

を行ってくれます。
また事後処理部は拡張することも可能です。
virtualenvがインストールされていなくてもbootstrap用のスクリプトがあれば
環境構築を行うことができます。
また、setuptoolsが入るのでその他に必要なライブラリをインストールしたり
することができます。
つまり開発に必要な環境を既存の環境を壊すことなく構築できるわけです。

例えば開発に必要なライブラリを大量に入れないといけないケースなどbootstrap
さえあればvirtualenv環境で必要なライブラリのインストールを一発で行うことが
できます。

開発の現場では誰かが一人、bootstrapを書き、そいつをみんなが実行することで
同じライブラリ、バージョンの環境が構築できるわけです。

bootstrapの構築

とはいえbootstrapスクリプトはどう書けばいいのでしょう?
virtualenvにはbootstrapを書くための仕組み用意されています。
create_bootstrap_script関数を使えば簡単に作ることができます。

例としてdjangoのbootstrapを書いてみます。

create_django_env.py
import os
import virtualenv

here = os.path.dirname(os.path.abspath(__file__))
base_dir = os.path.dirname(here)
script_name = os.path.join(base_dir, 'django-bootstrap.py')
extra_text = open(os.path.join(here,'_installer.py')).read()

def main():
    text = virtualenv.create_bootstrap_script(extra_text)
    if os.path.exists(script_name):
        f = open(script_name)
        cur_text = f.read()
        f.close()
    else:
        cur_text = ''
    print 'Updating %s' % script_name
    if cur_text == text:
        print 'No update'
    else:
        print 'Script changed; updating...'
        f = open(script_name, 'w')
        f.write(text)
        f.close()

if __name__ == '__main__':
    main()

拡張部は_installer.pyに記述します。

_installer.py
import os
import sys

#def extend_parser(parser):
#    parser.add_option(
#            '--pip',
#            dest='use_pip',
#            action='store_true',
#            default=False,
#            help='Use pip instead of easy_install (experimental)')

def after_install(options, home_dir):
    if sys.platform == 'win32':
        bin = "Scripts"
    else:
        bin = "bin"

    pip = os.path.join(home_dir,bin,'pip')
    easy_install = os.path.join(home_dir,bin,'easy_install')
    execute(easy_install,'pip')

    def execute(cmd,params):
        cmd = cmd + ' ' + params
        print "Running command...."
        print cmd
        subprocess.call(cmd.split())

    execute(pip,'install django')
    #execute(pip,'install python-mysql')
    #execute(pip,'install psycopg2')
    execute(pip,'install werkzeug')
    execute(pip,'install python-memcached')
    execute(pip,'install -e svn+http://django-command-extensions.googlecode.com/svn/trunk/')

とりあえずシンプルなもの。
DatabaseDriver部はコメントアウトしていますが、exetend_parser部で拡張して選択するようにもできます。
またpythonバージョンも指定することができますが今回はしていません。

create_django_env.pyを実行するとdjango-bootstrap.pyが出来ます。
このdjango-bootstrap.pyはvirtualenvと同じ引数を取ります。

python django-bootstrap.py --no-site-packages django-env

とやるとdjango-envという名前の環境を構築します。
構築する際に使うであろう

もまとめてインストールされます。

djangoだと依存が少ないのでありがたみが少ないですが、大量にライブラリをインストールするケースなどでは
かなり重宝します。

今のところbootstrapを提供しているものは少ないですが、今後増えてくるのではないかと思います。
(tg2がbootstrapを提供しています。大量に依存があるから)

まとめ

virtualenvを使う意義は

  • bootstrapによる環境の構築
  • python、ライブラリのバージョンを固定にできる環境を構築

だと思います。
特にbootstrapは複数人が同じ環境を構築するという作業を簡潔化してくれます。
(他にも何かを配布する際にも重宝する)
みんなもvirtualenv使おう!