py2exeとsetuptoolsの連携


## はじめに

py2exeを使って、windowsでexeファイルを作るとき、意外とdistutilsとの組み合わせで書かれているサイトが多い。しかし、Linuxなどでpythonを使っていると、パッケージ化するときに、まず間違いなくdistribute(setuptools)と組み合わせて使いたくなる。setuptoolsパッケージはdistutilsを強化するもので、setuptoolsを入れるとdistutilsのsetup()関数に与えられるパラメータが強化されたり、egg形式でのパッケージ配布が可能になったり、easy_installと呼ばれるコマンドラインツールが使えるようになったりする。

<del>このsetuptoolsは、最近、更新が止まっているのだが、その開発を引き継ぎ、完全互換なAPIを提供しているのがdistributeだ。今から使うなら、間違いなくdistributeを使ったほうが良い。早速、py2exeの説明をしたいところだが、おさらいもかねて、distributeから説明していく。（ちなみに、それぞれの概念は分離しているため、書くまでもないということなのかもしれない。そういう方にとっては、このページは役に立たない）</del>

親切な方のコメントで、distributeが時代遅れになったことが判明。巡り巡って、setuptoolsを使った方が良いとのこと。

* http://pythonhosted.org/distribute/

> Since the Setuptools 0.7 release, Setuptools and Distribute have merged and Distribute is no longer being maintained.


## setuptoolsの導入

まず、一般的なsetuptoolsの導入の仕方を紹介。

```
$ curl -O https://bitbucket.org/pypa/setuptools/downloads/ez_setup.py
$ sudo python ./dez_setup.py
```

setupスクリプトをダウンロードして実行するだけ。とても簡単。このパッケージを導入すると、easy_installというツールが使えるようになり、一般的なパッケージはこのツールを使って、site_packagesにインストールすることができるようになる（末尾のリンクを参照）。

ところで、ソースを配布するときは、setuptoolsのインストールが前提にできない。ドキュメントに「インストールしてね」と書くこともできるが、依存関係も解決できるpythonを使っているのに、そういうことをするのは、ちょっとカッコ悪い。そんな場合は、配布物にdistribute_setup.pyを含め、setup.pyの冒頭に以下のように記述する。

```python:setup.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import ez_setup
ez_setup.use_setuptools()
```

setup.pyの冒頭に上記のような記述をいれておくと、setup.pyのスクリプト導入時にdistributeパッケージのインストールを行う。


## setuptools+py2exeを組み合わせたsetup.pyの書き方

これまでの導入で、setuptoolsのインポートはできた。ここでは、setuptoolsとpy2exeを組み合わせたsetup.pyの書き方を紹介する。まず、最初にテンプレートは以下のとおりになる。

```python:setup.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

__author__  = "<author name>"
__author_email__ = "<author's email>"
__date__    = "<date>"
__version__ = "<version number>"

import os
from os import listdir
from os.path import join
from os.path import dirname
from os.path import splitext

import ez_setup
ez_setup.use_setuptools()

from setuptools import setup
import py2exe

def find_py_modules(dir):
    py_modules = []
    for f in listdir(dir):
        base, ext = splitext(f)
        if ext == '.py' and base != '<AppName>': py_modules.append(base)
    return py_modules

def read(fname):
    return open(join(dirname(__file__), fname)).read()

setup(name='VideoRecorder',
      version=__version__,
      description='<description>',
      author=__author__,
      author_email=__author_email__,
      url='<project homepage>',
      long_description=read('README'),
      include_package_data=True,
      console=[
        {
            'script': '<main python module>',
            'icon_resources': [(1, '<icon path>'),],
            },
        ],
      package_dir = {'': 'src'},
      py_modules=find_py_modules('src'),
      package_data={'':['*.ini', '*.gif', '*.ico']},
      data_files=[("", [<necessary dlls>]),],
      zipfile=None
      )
```

テンプレート中、<>で囲まれた部分は、作成者ごとに変更する部分である。以下にそのリストを挙げる。

|変数|概要|
|-----|------|
|author|作成者名|
|author's email|作成者のEメールアドレスです|
|date|日付|
|version number|バージョン番号|
|app name|主要pythonモジュールのパス|
|description|概要|
|project homepage|プロジェクトのホームページ|
|icon path|アイコンのパス|
|necessary dlls|必要なファイルの配列、使用するパッケージによっては、dllや、フォントなどを含める必要がある。|

プロジェクトに合わせて上記の変数は調整する必要がある。distributeからインポートされるメソッドは、setupである。setup.pyのパラメータは以下のとおり。

|パラメータ|概要|
|-------|-------|
|version|ソフトウェアのバージョン番号|
|description|ソフトウェアの概要説明|
|author|作成者名|
|author_email|作成者の連絡先|
|url|プロジェクトのurl|
|license|ライセンス情報（テンプレートでは未記載）|
|keywords|キーワード情報（テンプレートでは未記載）|
|long_description|ソフトウェアのより詳細な説明|
|console/windows|エラーメッセージ出力を望むか、コンソールプログラムを動作させるならconsoleパラメータとして、純粋なUIアプリとして、動作させるなら、windowsパラメータとして宣言する。|
|options|辞書のキーとして、compressed, optimize, bundle_filesを指定する。（テンプレートでは未記載）|
|include_package_data|CVSやSubversion、MANIFEST.inファイルに指定されているファイルを含めるかどうかを示すbool値|
|package_dir|パッケージの基底となるディレクトリ情報|
|package_data|パッケージ名と、ファイル名のパターンのリストを対応付ける辞書|
|py_modules|パッケージ化されていないpythonモジュール群の指定|
|data_files|純正pythonパッケージ以外で配布物に含めるファイル群の指定|
|zipfile|pythonのモジュール群をzipfile化するかどうか指定|

※他にも様々なパラメータがある。詳しくはサイト末尾のリンクから調べると良い。

その他のメソッドについて、下記にその意味を解説していく。なお、これらの関数とテンプレートの中の<>変数に関しては、必須ではないので、プロジェクトに合わせて、各自変更すれば良い。

```python
def find_py_modules(dir):
    py_modules = []
    for f in listdir(dir):
        base, ext = splitext(f)
        if ext == '.py' and base != '<AppName>': py_modules.append(base)
    return py_modules
```

find_py_modulesは、find_packagesのモジュール版（も簡易実装）である。外部パッケージを組み合わせたアプリケーションの場合、コアとなるソースはパッケージにするまでもない単位である場合がある。そのようなときは、packagesパラメータと、find_packagesメソッドの代わりに、py_modulesパラメータと、find_py_modulesメソッド（中身は各自、調整すること）を組み合わせると良い。

```python
def read(fname):
    return open(join(dirname(__file__), fname)).read()
```

readメソッドは、long_descriptionの内容をファイルから読み込むためのメソッドである。descriptionでは説明が足りない規模のアプリケーションを作る場合に便利なメソッドである。

## リンク

setuptoolsそのものに関しては、正確で包括的な情報を有するブログがあるので、こちらを参照してほしい。

* [SumiTomohikoの日記](http://d.hatena.ne.jp/SumiTomohiko/20070609/1181406701)

このサイトを作るときもこのサイトを参考にした。setuptoolsを扱う上で、起点となるサイトだと思う。その他、easy_installとdistributeに関してのリンクは以下のとおり。両方共、英語のサイトなので、もっと簡単な解説が欲しい場合は、以下のキーワード（+python）でGoogleで検索すると良い。

* <del>[distributeについて](http://pypi.python.org/pypi/distribute)</del>
* [ez_setupについて](https://bitbucket.org/pypa/setuptools)
* [easy_installについて](https://pythonhosted.org/setuptools/easy_install.html)