YAMAGUCHI::weblog

土足で窓から失礼いたします。今日からあなたの息子になります。 当年とって92歳、下町の発明王、エジソンです。

setuptoolsを使うときはsetuptoolsではなくてdistributeを使え!

注意 (2013.11.11)

この記事はもう古いので、いまはdistributeではなくてsetuptoolsをインストールしてください。setuptoolsはプロジェクトが再始動してdistributeよりも新しい実装になっています。

はじめに

こんにちは!Python界のポルナレフです。

         ,. -‐'''''""¨¨¨ヽ
         (.___,,,... -ァァフ|          あ…ありのまま 今 起こった事を話すぜ!
          |i i|    }! }} //|
         |l、{   j} /,,ィ//|       『おれがsetuptoolsを使ったらsetuptoolsのsetuptoolsは
        i|:!ヾ、_ノ/ u {:}//ヘ        バグっていてdistributeのsetuptoolsはちゃんと動いた!』
        |リ u' }  ,ノ _,!V,ハ |
       /´fト、_{ル{,ィ'eラ , タ人        な… 何を言ってるのか わからねーと思うが
     /'   ヾ|宀| {´,)⌒`/ |<ヽトiゝ        おれも 何が起きたか わからなかった…
    ,゙  / )ヽ iLレ  u' | | ヾlトハ〉
     |/_/  ハ !ニ⊇ '/:}  V:::::ヽ        頭がどうにかなりそうだった…
    // 二二二7'T'' /u' __ /:::::::/`ヽ
   /'´r -&#8212;一ァ‐゙T´ '"´ /::::/-‐  \   typoだとか設定ミスだとか
   / //   广¨´  /'   /:::::/´ ̄`ヽ ⌒ヽ    そんなチャチなもんじゃあ 断じてねえ
  ノ ' /  ノ:::::`ー-、___/::::://       ヽ  }
_/`丶 /:::::::::::::::::::::::::: ̄`ー-{:::...       イ  もっと恐ろしいものの 片鱗を味わったぜ…

何が起きたのか見てください。

結論

先に結論だけ言っておきます。

[11/01/16 0:01:33] しみずかわ: なんか、pth_file.add()の実装にバグがあって、カレントディレクトリが追加対象dirの場合に追加してくれない、という現象っぽい

setuptools.egg内のsetuptoolsにはバグがある模様です。特にsetup.py developの時に起きるので開発中はdistribute.eggを使うほうが良さそうです。virtualenvを使っているときは--distributeを指定しましょう。virtualenvwrapperでも同様です。

$ python virtualenv.py --distribute ENV名
$ mkvirtualenv --distribute ENV名

夜中にお付き合いいただいた清水川先生有難うございます!ここで宣伝させていただきます!

宣伝

エキスパートPythonプログラミングにはPythonパッケージのリリースの仕方なども詳細に解説されています。ぜひご一読ください。僕も愛読中です!

エキスパートPythonプログラミング
Tarek Ziade (著), 稲田 直哉 (翻訳), 渋川 よしき (翻訳),
清水川 貴之 (翻訳), 森本 哲也 (翻訳)
アスキー・メディアワークス
売り上げランキング: 6409

再現環境

  • ホスト環境
OS Mac OS X 10.6.6
Python 2.6.6
virtualenv 1.5.1
virtualenvwrapper 2.5.3
  • virtualenv環境1
Python 2.6.6
setuptools setuptools-0.6c11-py2.6
  • virtualenv環境2
Python 2.6.6
setuptools distribute-0.6.14-py2.6

状況

次のディレクトリ構成でpython setup.py developを使ってjinja2をインストールする。docs内でimport jinja2出来るようにしたい。

$ tree -L 1
.
├── CHANGES
├── Jinja2.egg-info
├── docs
├── jinja2
└── setup.py

で、何が起きたかと言うと、普通なら問題なくimportできるはずが、できない、という状況。

再現

setuptoolsを使った場合

まずは普通にvirtualenvをしたところで実行してみる。

$ mkvirtualenv virtualenv1
(virtualenv1)$ python setup.py -vvv develop
...
reading manifest file 'Jinja2.egg-info/SOURCES.txt'
writing manifest file 'Jinja2.egg-info/SOURCES.txt'
running build_ext
Creating /Users/ymotongpoo/.virtualenvs/virtualenv1/lib/python2.6/site-packages/Jinja2.egg-link (link to .)
Adding Jinja2 2.6 to easy-install.pth file

Installed /Users/ymotongpoo/docs/jinja2_docjp
Processing dependencies for Jinja2==2.6
Finished processing dependencies for Jinja2==2.6

あれ、verboseにしてるのにpthファイルの絶対パスが表示されない。ほんとにpthファイルに書きこまれたのかな...?みてみよう。

(virtualenv1)$ pwd
/Users/ymotongpoo/.virtualenvs/virtualenv1/lib/python2.6/site-packages
(virtualenv1)$ cat easy-install.pth 
import sys; sys.__plen = len(sys.path)
./setuptools-0.6c11-py2.6.egg
./pip-0.8.1-py2.6.egg
import sys; new=sys.path[sys.__plen:]; del sys.path[sys.__plen:]; p=getattr(sys,'__egginsert',0); sys.path[p:p]=new; sys.__egginsert = p+len(new)

あれ、書かれてない...どうしてだろう?ソースコードを見てみます。

  • setuptools-0.6c11-py2.6.egg/command/easy_install.py
    def update_pth(self,dist):
            ...
            else:
                log.info("Adding %s to easy-install.pth file", dist)
                self.pth_file.add(dist) # add new entry
                if dist.location not in self.shadow_path:
                    self.shadow_path.append(dist.location)

        if not self.dry_run:

            self.pth_file.save()

            if dist.key=='setuptools':
                # Ensure that setuptools itself never becomes unavailable!
                # XXX should this check for latest version?
                ...

なのでself.pth_file.save()を見てみます。

    def save(self):
        """Write changed .pth file back to disk"""
        if not self.dirty:
            return

        data = '\n'.join(map(self.make_relative,self.paths))
        if data:
            log.debug("Saving %s", self.filename)
            ...
            f.write(data); f.close()

        elif os.path.exists(self.filename):
            log.debug("Deleting empty %s", self.filename)
            os.unlink(self.filename)
       
        # ここの節追加。
        else:
            log.info("bucho!, %s", self.filename)

        self.dirty = False

ここで"Saving〜"っていう出力が先程のログにないから、上のソースにあるみたいにelse節を追加して再度setup.py develop実行したけど、やっぱりログが表示されない。pthも更新されていない。当然この状態でdocsディレクトリに行ってもimportは出来ない。

(virtualenv1)$ pwd
/Users/ymotongpoo/docs/jinja2_docjp/docs
(virtualenv1)$ python -c "import jinja2; print jinja2.__version__"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named jinja2
distributeを使った場合

今度はdistributeを使ったvirtualenv環境を作って実行してみる。

$ mkvirtualenv --distribute virtualenv2

これで再度setup.py developしてみる。

(virtualenv2)$ python setup.py -vvv develop
...
writing manifest file 'Jinja2.egg-info/SOURCES.txt'
running build_ext
Creating /Users/ymotongpoo/.virtualenvs/virtualenv2/lib/python2.6/site-packages/Jinja2.egg-link (link to .)
Adding Jinja2 2.6 to easy-install.pth file
Saving /Users/ymotongpoo/.virtualenvs/virtualenv2/lib/python2.6/site-packages/easy-install.pth

Installed /Users/ymotongpoo/docs/jinja2_docjp
Processing dependencies for Jinja2==2.6
Finished processing dependencies for Jinja2==2.6

今度はちゃんと"Saving〜"が表示されている!でeasy-install.pthも確認する。

(virtualenv2)$ cat /Users/ymotongpoo/.virtualenvs/jinja2doc2/lib/python2.6/site-packages/easy-install.pth
import sys; sys.__plen = len(sys.path)
./distribute-0.6.14-py2.6.egg
./pip-0.8.1-py2.6.egg
/Users/ymotongpoo/docs/jinja2_docjp
import sys; new=sys.path[sys.__plen:]; del sys.path[sys.__plen:]; p=getattr(sys,'__egginsert',0); sys.path[p:p]=new; sys.__egginsert = p+len(new)

ちゃんとjinja2_docjpが書きこまれている!そしてdocsディレクトリでjinja2をインポートしてみる。

(virtualenv2)$ pwd
/Users/ymotongpoo/docs/jinja2_docjp/docs
(virtualenv2)$ python -c "import jinja2; print jinja2.__version__"
2.6

出来ました!

追記 (2011.01.17)

今日@aodag先生が環境変数でvirtualenvが使うsetuptoolsをどのモジュールのものにするか選択する方法を教えてくれました。

[16:59:34] aodag: export VIRTUALENV_USE_DISTRIBUTE=1

環境変数でdistribute使うように設定しておくともっと幸せになれるそうです。@aodag先生ありがとうございます!