1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

python setup.py bdist_egg --exclude-source-files が動かないPython3と対処法

Last updated at Posted at 2016-11-08

背景

egg配布でPythonのソースコードを「含めない」という方法として、bdist_egg --exclude-source-filesと指定する方法があります。ありました。

例えば次のプロジェクトを例に。

python setup.py bdist_egg --exclude-source-files を実行するとeggが出来ます。
ただしPython 3系でこのeggをeasy_installしても動作しないのです。
--exclude-source-filesを指定しなければGreeting!という文字を出すだけのgreetingコマンドが手に入ります。

何故

要するに__pycache__下のpycだけではPython3のコードは動作しないのです。

以下の、特にPEP 3147と図を御覧ください。

image

図によれば.pyがあった上で__pycache__を引きます。
一方--exclude-source-filesを指定すると.pyだけ抜けるので、PEP 3147のフローから見るとImportError一直線です。
__init__.py相当のものもないのでモジュールとして認識しない( ー`дー´)

これを理解した上で、setuptoolsのbdist_egg実装をさっと読んでみますと、distutilsのバイトコンパイル機能を使ってるのです。が、これがlegacyオプション対応してない。固定で__pycache__にバイトコンパイル結果を突っ込んでしまうようです。compileall.compile_dirとか使ってくれてれば簡単な修正で済むんですががが

ワークアラウンド

bdist_egg --exclude-source-filesによって壊れた状態のeggファイルをzipファイルとして読みこんで、__pycache__/ 下にいるキャッシュとしてのpycを、レガシーな位置にむりやり引きずり出せば動作するようになります。

以下はPython 3.5.2上での実装例。importは省略。

tweak_egg.py
RE_PYCACHE_FILE = re.compile('(.+/)__pycache__/(.+)\.cpython-3\d(.pyc)$')

with tempfile.TemporaryDirectory() as tmpdir:
    names_lst = []
    tmpdir_abs_path = os.path.abspath(tmpdir)
    with ZipFile(egg_path, 'r') as zf:
        for name in zf.namelist():
            m = RE_PYCACHE_FILE.match(name)
            if m:
                w_name = m.group(1) + m.group(2) + m.group(3)
            else:
                w_name = name
            w_path = os.path.join(tmpdir_abs_path, w_name)
            dir_path = os.path.dirname(w_path)
            if not os.path.exists(dir_path):
                os.makedirs(dir_path)
            with open(w_path, 'wb') as file_to_write:
                with zf.open(name) as file_to_read:
                    file_to_write.write(file_to_read.read())
            names_lst.append(w_name)
    with ZipFile(egg_path, 'w', compression=ZIP_DEFLATED) as zf:
        for name in names_lst:
            r_path = os.path.join(tmpdir_abs_path, name)
            zf.write(r_path, arcname=name)

おまけ

上のような事実に気づく前に果敢にバグ報告したら、意外なクローズのされ方をされてむしろ驚きました。

setuptoolsはここで報告しても意味ないのか。そっちかよ。

# しかし前回もそうだったけど、初動がめっちゃ早いなぁ……偉大だ。

出来れば上記を取りまとめて再度setuptools本家に報告する予定ですが、とりあえずそういうことがあったということで。

追記(2016-11-09 16:50): 報告しました。 https://github.com/pypa/setuptools/issues/844

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?