Python のパッケージング
はじめに
アプリケーションのインストール方法には二つの流儀があります。一つ目の流儀は Windows と Mac OS X で一般的なもので、アプリケーションは必要なものを全て含むべきであり、インストールするときには何にも依存してはいけないという考え方です。この考え方ではアプリケーションの管理が単純になります: アプリケーションはスタンドアローンの“ツール”であり、インストールおよびアンインストールで OS の他の部分がめちゃくちゃになることもありません。もしアプリケーションが一般的でないライブラリを必要とするなら、そのライブラリはアプリケーションのディストリビューションに含まれます。
二つ目の流儀は Linux ベースのシステムで標準的なもので、ソフトウェアをパッケージと呼ばれる必要なものを全て含んだ小さな単位の集まりと扱います。いくつかのライブラリがまとめられてパッケージとなり、このライブラリパッケージが他のパッケージに依存することもあります。アプリケーションのインストールには他の多数のライブラリの特定のバージョンを検索・インストールする必要があり、依存ライブラリは通常数千個のライブラリを収めた中央レポジトリからフェッチされます。こういった考え方があるために、Linux のディストリビューションは dpkg
や RPM
といったパッケージ管理システムを使って依存関係を追跡し、同じライブラリの互換性の無い二つのバージョンがインストールされるのを防いでいます。
両方の流儀に利点と欠点があります。全ての部分を個別に更新・置換できる高度にモジュール化されたシステムがあれば、各ライブラリが一つだけ存在するようになってパッケージの管理が簡単になります。さらにアプリケーションが利用するライブラリを個別に更新することも可能であり、例えばあるライブラリのセキュリティフィックスはそのライブラリを利用する全てのアプリケーションにすぐに伝わります。これに対してアプリケーションが独自にライブラリを持っていた場合、特に異なるアプリケーションが異なるバージョンのライブラリを使っていた場合には、セキュリティフィックスのデプロイが困難になります。
しかしこのモジュール性を欠点だと考える開発者もいます。開発者自身が依存関係を制御できないためです。システムのアップグレードで起こる“依存関係地獄”に影響されることのない安定なアプリケーション動作環境を保証するには、アプリケーションをスタンドアローンのソフトウェアツールとして提供した方が簡単です。
アプリケーションに必要なものを全て含めるようにすると、複数のオペレーティングシステムをサポートする場合にも開発者の仕事が簡単になります。プロジェクトの中には、独自のディレクトリ構造内で動作し、ログファイルでさえホストのシステムと一切対話しないで動作するポータブルなアプリケーションをリリースしているものもあります。
Python のパッケージングシステムは二つ目の流儀 ――インストールごとに複数の依存関係―― を、開発者、管理者、パッケージャー、ユーザーにとってできる限り使いやすくすることを目指しています。しかし残念ながらこのシステムには様々な問題があり、バージョンスキーマが直感的でない、上手く扱えないデータファイルがある、再パッケージングが難しいなど様々な問題が生じます。三年前、私は Pythoneer のグループと共にパッケージングシステムを再開発してこういった問題を解決することを決心しました。この章ではパッケージング同盟 (the Fellowship of the Packaging) と自称する私たちが修復しようとしている問題、およびそういった問題に対する私たちの解決法について説明します。
Python においてパッケージという言葉は Python ファイルを含むディレクトリを指し、モジュールという言葉は一つの Python ファイルを指します。“パッケージ”という言葉をシステムが使った場合には通常のプロジェクトのリリースを指すこともあるので、この用語には曖昧さがあります。
Python 開発者もはっきりしないまま話をすることがあります。この曖昧さを取り除く一つの方法は、Python モジュールを含んだディレクトリを“Python パッケージ”と呼ぶことです。なお“リリース”はプロジェクトのあるバージョンを指し、“ディストリビューション”はソースまたはバイナリのリリースを tarball や zip ファイルにした配布できる形式を指します。
Python 開発者の苦しみ
多くの Python プログラマーは自身のプログラムが全ての環境で実行できることを望みながらも、Python の標準ライブラリとシステム固有のライブラリの両方を利用するのが普通です。しかしありとあらゆるパッケージングシステム用にアプリケーションのパッケージを個別に作成するのは不可能なので、Python 固有のリリースを作成しなければなりません。Python 固有のリリースとは、オペレーティングシステムに関わらず Python のインストールディレクトリの内部にインストールされるリリースのことです。その上で、次のことを祈るわけです:
-
全てのターゲットシステムのパッケージャがあなたのプログラムを再パッケージできること。
-
依存するライブラリが全てのターゲットシステムで再パッケージされていること。
-
システムの依存関係が明確に記述されていること。
これが不可能な場合もあります。例えば Plone (Python で書かれた高機能な CMS) は数百個の小さな Python ライブラリを使っており、全てのライブラリがパッケージシステムで利用可能なわけではありません。そのため Plone はポータブルアプリケーションの中に必要なライブラリを全て含める必要があります。これを行うために Plone は zc.buildout
というプログラムを利用しており、このプログラムを使って全ての依存関係を調べ、任意のシステムで実行できるポータブルなアプリケーションを単一ディレクトリに作成します。C コードもここでコンパイルされるので、出来上がるディレクトリが事実上のバイナリリリースとなります。
これは開発者からすると非常に便利です: 依存関係を後で説明する Python の標準的な記法で記述すれば、あとは zc.buildout
を使ってアプリケーションをリリースできます。しかし前述の通り、このようにリリースを行うとシステム内に同じ内容のファイルを含んだ“要塞”がいくつも構築されることになります。これは Linux システム管理者が大嫌いなものです。Windows の管理者は気にしないかもしれませんが、CentOS や Debian の管理者は気にします。なぜならこういったシステムでは、システム内の全てのファイルが登録され、分類され、管理者のツールに知られているという仮定の下でシステムの管理が行われるからです。
システムの管理者はあなたのアプリケーションをシステムの標準的な方法で再パッケージします。ここで答えなければならないのが、「Python のパッケージシステムを他のパッケージシステムに自動的に変換できるか?」という問題です。もしこれができるなら、アプリケーションやライブラリは任意のシステムへと追加のパッケージング作業無しにインストールできます。ここで“自動的に”というのは作業の全てがスクリプトに行われるというわけでは必ずしもありません。RPM
や dpkg
などのパッケージャではそれは不可能であり、再パッケージするプロジェクトにちょっとした情報を埋め込まなければならないからです。また開発者がパッケージに関する基本的なルールを理解していないために、再パッケージに手間がかかってしまうこともよくあります。
既存の Python のパッケージシステムを使ってパッケージャを苦しめる例を一つ示しましょう: “MathUtils”という名前のライブラリを“Fumanchu”というバージョン名でリリースすることです。このライブラリを書いた聡明な数学者は、自身のプロジェクトのバージョン名に飼っているネコの名前を付けるのがイケていると思ったのでしょう。しかし“Fumanchu”が彼の二匹目のネコで、最初のネコの名前が“Phil”であり、“Fumanchu”は“Phil”の後に来なければならないことを、パッケージャはどうやって関知しろというのでしょうか?
この例は極端に思えるかもしれませんが、現在使われているツールと標準で起こる可能性のある問題です。最悪なのは、easy_install
や pip
といったツールが自身の標準的でないレジストリを使ってインストールされたファイルを記録しており、“Fumanchu”や“Phil”といったバージョン名を辞書順にソートしてしまう場合です。
もう一つの問題が、データファイルをどう扱うかというものです。例えばアプリケーションが SQLite データベースを使っていたら? データベースをパッケージディレクトリに配置したとすると、システムがそのツリーへの書き込みを禁じている場合にはアプリケーションが落ちてしまうかもしれません。仮に落ちなかったとしても、アプリケーションのバックアップデータの保存場所 (/var
) に関する Linux の慣習を破ることになります。
現在のパッケージングのアーキテクチャ
Distutils
パッケージは Python の標準ライブラリに含まれており、これまでに説明したような問題に対処します。Distutils
が Python における標準なので、人々は欠点と共にこのツールと付き合っています。より洗練されたツールもいくかあり、Setuptools
は Distutils
の上に機能を追加したライブラリ、Distribute
は Setuptools
のフォークです。また Setuptools
に依存するさらに高機能なインストーラ Pip
もあります。
しかしこういった新しいツールは全て Distutils
の上に作られており、その問題も受け継いでいます。Distutils
の修復も試みられましたが、上手く行きませんでした。他のツールが Distutils
のコードをあまりにも深く使っているために、その内部でさえ少しでも変更してしまうと Python のパッケージングエコシステムが傷つけられてしまうのです。
そのため私たちは Distutils
を凍結し、後方互換性をあまり気にしない Distutils2
の開発を同じコードベースで始めることになりました。何を変えたか、そしてなぜ変えたかを理解するために、まずは Distutils
について詳しく見ていきます。
Distutils
の基礎と設計の問題
Distutils
のコマンドは run
メソッドを持ったクラスとして表され、run
はオプションと共に呼び出されます。Distutils
はこの他にも全てのコマンドが参照できるグローバル変数を保持する Distribution
クラスも提供します。
Distutils
を使用するには、開発者はプロジェクトに Python モジュールを一つ追加します。このモジュールは慣習的に setup.py
と呼ばれ、Distutils
のメインのエントリーポイントである setup
関数の呼び出しを含みます。この関数に付いているたくさんのオプションは Distribution
のインスタンスに渡され、コマンドから使用されます。次に示すのは、名前とバージョン、モジュールのリストという基本的なオプションを定義する例です:
from distutils.core import setup
setup(name='MyProject', version='1.0', py_modules=['mycode.py'])
そしてこのモジュールを Distutils
のコマンドから使用します。例えば sdist
コマンドを使うとソースディストリビューションをアーカイブで作成し、dist
ディレクトリに配置できます。
$ python setup.py sdist
install
コマンドを使えばプロジェクトをインストールを同じスクリプトを使って実行できます:
$ python setup.py install
Distutils
には他にも次のようなコマンドがあります:
-
upload
: ディストリビューションをオンラインのレポジトリにアップロードする。 -
register
: ディストリビューションをアップロードすることなくプロジェクトのメタデータをオンラインレポジトリに登録する -
bdist
: バイナリディストリビューションを作成する。 -
bdist_msi
: Windows 用の.msi
ファイルを作成する。
プロジェクトに関する情報はコマンドラインオプションを使って取得できます。
プロジェクトのインストールやプロジェクトの情報の取得は必ず Distutils
を setup.py
に対して起動することで行われます。例えばプロジェクトの名前を取得するには次のようにします:
$ python setup.py --name
MyProject
つまりプロジェクトをビルドするのであれ、公開するのであれ、インストールするのであれ、setup.py
がプロジェクトととの対話口となります。開発者はプロジェクトの内容を関数に対するオプションに記述し、そのファイルをパッケージングに関する全てのタスクに使用します。このファイルはインストーラがターゲットのシステムにプロジェクトをインストールするときにも使われます。
パッケージング、リリース、インストールに単一の Python モジュールを使うことは、Distutils
の大きな問題の一つです。例えば lxml
プロジェクトの name
を取得しようとすると、setup.py
は単純な文字列を返す他にも様々な処理を行います:
$ python setup.py --name
Building lxml version 2.2.
NOTE: Trying to build without Cython, pre-generated 'src/lxml/lxml.etree.c'
needs to be available.
Using build configuration of libxslt 1.1.26
Building against libxml2/libxslt in the following directory: /usr/lib/lxml
ユーザーは setup.py
をインストールにしか使用せず、Distutils
の他の機能は開発中にしか使わないと開発者が思い込んでいるために、上述のコマンドが失敗してしまう場合さえあるかもしれません。setup.py
に複数の役割があるために、こういった思い違いが起きがちです。
メタデータと PyPI
Distutils
がディストリビューションをビルドすると、PEP 3141 で表される標準に沿った Metadata
ファイルが作成されます。このファイルには静的なバージョンの一般的なメタデータ、例えばプロジェクトの名前やリリースのバージョンが含まれます。メタデータの主なフィールドを以下に示します:
Name
: プロジェクトの名前。Version
: リリースのバージョン。Summary
: 簡単な説明。Description
: 詳細な説明。Home-Page
: プロジェクトの URL。Author
: 著者の名前。Classifiers
: プロジェクトの分類。Python はライセンスやリリースの成熟度 (beta, alpha, final) に関する分類を提供している。Requires
,Provides
,Obsoletes
: モジュール間の依存関係。
これらのフィールドのほとんどは、他のパッケージングシステムにも同じような要素があります。
Python Package Index2 (PyPI) は CPAN のようなパッケージの中央レポジトリであり、プロジェクトの登録と公開は Distutils
の register
コマンドと upload
コマンドを使って行います。register
コマンドは Metadata
ファイルをビルドし、それを PyPI に送信します。送信されたデータはウェブページやウェブサービスを通してインストーラなどのツールおよび人間が閲覧できるようになります。
PyPI ではプロジェクトを Classifiers
ごとに閲覧でき、著者の名前やプロジェクトの URL も取得できます。それ以外にも、Requires
フィールドを使って Python モジュールの依存関係を定義することが可能です。プロジェクトに Requires
メタデータを追加するには setup
関数の requires
オプションを使います:
from distutils.core import setup
setup(name='foo', version='1.0', requires=['ldap'])
この例における ldap
モジュールに対する依存関係の定義は完全に宣言的であり、ツールやインストーラがそのモジュールの存在を確認することはありません。このやり方は Perl のように require
キーワードを通してモジュールレベルの依存関係を定義するようになっていれば問題ありません。もしそうならインストーラが PyPI の中で依存関係を探してそれをインストールすればよいだけであり、CPAN は基本的にこれを行っています。しかし Python では ldap
というモジュールが任意の Python プロジェクトに表れる可能性があるために、こうすることはできません。つまり Distutils
が複数のパッケージやモジュールを含むプロジェクトをリリースできるようにしているために、この Requires
というメタデータフィールドの存在意義が全く無くなっているのです。
Metadata
ファイルに関するもう一つの問題点は、このファイルが Python スクリプトによって作られるために、ファイルの内容が実行されるプラットフォーム固有になってしまう点です。例えば Windows 用の機能を提供するプロジェクトでは、setup.py
を次のように書きます:
from distutils.core import setup
setup(name='foo', version='1.0', requires=['win32com'])
しかしこうすると、たとえポータブルな機能があったとしてもプロジェクト全体が Windows 上でのみ動作するようになってしまいます。この問題は一見すると、requires
オプションを Windows のときだけ付けるようにすれば解決するように見えます:
from distutils.core import setup
import sys
if sys.platform == 'win32':
setup(name='foo', version='1.0', requires=['win32com'])
else:
setup(name='foo', version='1.0')
しかしこうすると問題はさらに悪くなります。ソースのアーカイブはこのスクリプトを使って作られ、その後 PyPI へと公開されることを思い出してください。つまり PyPI へと送られる静的な Metadata
ファイルは、プロジェクトをコンパイルするプラットフォームに依存することになります。これを言い換えれば、あるモジュールがプラットフォーム固有なことをメタデータで静的に指定する方法は存在しないということです。
PyPI のアーキテクチャ
前述の通り PyPI は Python プロジェクトの中央インデックスであり、既存のプロジェクトをカテゴリごとに閲覧したり自身のプロジェクトを登録したりといったことを誰でも行うことができます。ソースまたはバイナリの形でディストリビューションをアップロードして既存のプロジェクトに追加したり、それをダウンロードしてインストールや研究のために使うことができます。PyPI では他にも、インストーラなどのツールが利用するためのウェブサービスも提供されています。
プロジェクトの登録とディストリビューションのアップロード
PyPI へのプロジェクトの登録は Distutils
の register
コマンドで行われます。このコマンドはプロジェクトのメタデータやバージョン情報を含んだ POST リクエストを生成します。PyPI は登録されるプロジェクトを PyPI の登録ユーザーと結び付けるので、リクエストには Basic 認証用のヘッダーも付きます。認証情報は Distutils
の設定としてローカルに保存されるか、register
コマンドを起動したときにプロンプトから入力されます。次に使用例を示します:
$ python setup.py register
running register
Registering MPTools to http://pypi.python.org/pypi
Server response (200): OK
登録されたプロジェクトには HTML バージョンのメタデータを含んだウェブページが割り当てられます。パッケージャがディストリビューションを PyPI にアップロードするときには upload
コマンドが使われます:
$ python setup.py sdist upload
running sdist
...
running upload
Submitting dist/mopytools-0.1.tar.gz to http://pypi.python.org/pypi
Server response (200): OK
ファイルを PyPI に直接アップロードする代わりに、メタデータの Download-URL
フィールドを使ってユーザーに他の場所を伝えることも可能です。
PyPI へのクエリ
PyPI はウェブユーザーのための HTML ページの他にも、プロジェクトを閲覧するツール用のサービスを二つ提供しています: Simple Index プロトコルと XML-RPC API です。
Simple Index プロトコルのルートは http://pypi.python.org/simple/ であり、このページは登録されている全てのプロジェクトへの相対リンクを含んだプレーンな HTML ページです:
<html><head><title>Simple Index</title></head><body>
... ... ...
<a href='MontyLingua/'>MontyLingua</a><br/>
<a href='mootiro_web/'>mootiro_web</a><br/>
<a href='Mopidy/'>Mopidy</a><br/>
<a href='mopowg/'>mopowg</a><br/>
<a href='MOPPY/'>MOPPY</a><br/>
<a href='MPTools/'>MPTools</a><br/>
<a href='morbid/'>morbid</a><br/>
<a href='Morelia/'>Morelia</a><br/>
<a href='morse/'>morse</a><br/>
... ... ...
</body></html>
例えば MPTools プロジェクトには MPTools/
というリンクが付いており、このディレクトリにプロジェクトがあることを意味します。リンク先のページにはプロジェクトに関する情報が全て載っています:
-
PyPI に保存されている全てのディストリビューション。
-
登録されたプロジェクトの各バージョンの
Metadata
で定義されるHome-URL
へのリンク。 -
登録されたプロジェクトの各バージョンの
Metadata
で定義されるDownload-URL
へのリンク。
例えば MPTools のページは次のようになっています:
<html><head><title>Links for MPTools</title></head>
<body><h1>Links for MPTools</h1>
<a href="../../packages/source/M/MPTools/MPTools-0.1.tar.gz">MPTools-0.1.tar.gz</a><br/>
<a href="http://bitbucket.org/tarek/mopytools" rel="homepage">0.1 home_page</a><br/>
</body></html>
したがってインストーラなどのツールがプロジェクトのディストリビューションを検索したい場合には、インデックスページを検索するか、あるいは http://pypi.python.org/simple/PROJECT_NAME/ が存在するかを調べることになります。
このプロトコルには主な欠点が二つあります。まず、現在の PyPI はシングルサーバーです。たいていのユーザーはディストリビューションのローカルコピーを作成しますが、それでも過去二年間に何度かあったダウンタイムには、ビルド時に依存プロジェクトを PyPI で検索するインストーラを定期的に使う必要がある開発者は作業が行えなくなりました。例えば Plone アプリケーションをビルドするときには PyPI へのクエリが全部で数百個生成されます。このような状況では、PyPI が単一障害点となる可能性があります。
次に、ディストリビューションが PyPI に含まれておらず Simple Index ページに Download-URL
が設定されている場合、インストーラはそのリンクをたどりますが、そのリンクがまだ生きていること、および本当にリリースが提供されていることについては、そうであるようにと祈るしかありません。このたらい回しがある限り、Simple Index を使った処理の信頼性は落ちます。
Simple Index プロトコルの目標は、プロジェクトをインストールするときに使用するリンクのリストをインストーラに渡すことです。プロジェクトのメタデータはここで公開されず、登録されたプロジェクトの追加情報は XML-RPC メソッドを使って取得します:
>>> import xmlrpclib
>>> import pprint
>>> client = xmlrpclib.ServerProxy('http://pypi.python.org/pypi')
>>> client.package_releases('MPTools')
['0.1']
>>> pprint.pprint(client.release_urls('MPTools', '0.1'))
[{'comment_text': &rquot;,
'downloads': 28,
'filename': 'MPTools-0.1.tar.gz',
'has_sig': False,
'md5_digest': '6b06752d62c4bffe1fb65cd5c9b7111a',
'packagetype': 'sdist',
'python_version': 'source',
'size': 3684,
'upload_time': <DateTime '20110204T09:37:12' at f4da28>,
'url': 'http://pypi.python.org/packages/source/M/MPTools/MPTools-0.1.tar.gz'}]
>>> pprint.pprint(client.release_data('MPTools', '0.1'))
{'author': 'Tarek Ziade',
'author_email': 'tarek@mozilla.com',
'classifiers': [],
'description': 'UNKNOWN',
'download_url': 'UNKNOWN',
'home_page': 'http://bitbucket.org/tarek/mopytools',
'keywords': None,
'license': 'UNKNOWN',
'maintainer': None,
'maintainer_email': None,
'name': 'MPTools',
'package_url': 'http://pypi.python.org/pypi/MPTools',
'platform': 'UNKNOWN',
'release_url': 'http://pypi.python.org/pypi/MPTools/0.1',
'requires_python': None,
'stable_version': None,
'summary': 'Set of tools to build Mozilla Services apps',
'version': '0.1'}
このアプローチの問題点は、XML-RPC API を使って公開しているデータの中には Simple Index のページで公開できる静的なファイルも含まれる点です。そのようなファイルは Simple Index のページで公開した方がクライアントツールが単純になり、さらに PyPI がクエリを処理する必要もなくなります。
Python インストールのアーキテクチャ
Python プロジェクトを python setup.py install
でインストールすると、標準ライブラリに含まれる Distutils
がプロジェクトのファイルをユーザーのシステムにコピーします。
-
Python パッケージとモジュールは、インタープリタが起動したときに読み込まれる Python ディレクトリに移動します。このディレクトリは Ubuntu の最新版では
/usr/local/lib/python2.6/dist-packages/
で、Fedora では/usr/local/lib/python2.6/sites-packages/
です。 -
プロジェクトで定義されるデータファイルはシステム内の任意の場所に移動できます。
-
プロジェクト内の実行可能なスクリプトはシステムの
bin
ディレクトリに移動します。このディレクトリはプラットフォームによって異なりますが、Linux であれば通常は/usr/local/bin
または Python のインストール場所のbin
ディレクトリです。
Python 2.5 以降ではモジュールとパッケージと共にメタデータファイルもコピーされ、project-version.egg-info
という名前のファイルとなります。例えば virtualenv
プロジェクトには virtualenv-1.4.9.egg-info
というファイルがあるでしょう。このメタデータファイルをプロジェクトごとに見ていけばシステムにインストールされているプロジェクトのバージョン付きリストが手に入るので、このファイルはインストールされたプロジェクトに関するデータベースとなります。ただし Distutils
はシステムにインストールするファイルのリストを記録しません。これを言い換えると、システムにコピーした全てのファイルを消去する方法は存在しません。install
コマンドにはインストールするファイルをテキストで保存する --record
オプションがあるのを考えれば、これは残念なことです。このオプションはデフォルトで無効であり、Distutils
のドキュメントはほとんどこのオプションに触れていません。
Setuptools
, Pip
など
章の最初で触れた通り、Distutils
が持つ問題点の修正を試みたプロジェクトがいくつかあります。成功の度合いは様々です。
依存関係の問題
PyPI では、複数のモジュールからなる Python パッケージをいくつかまとめたものを Python プロジェクトとして公開できます。しかし同時に、プロジェクトはモジュールレベルの依存関係を Require
を使って定義できます。どちらのアイデアも悪くありませんが、両方を同時に使うというのは良くありません。
必要なのはプロジェクトレベルの依存関係を定義する仕組みであり、Distutils
の上に機能を追加する Setuptools
はちょうどこれを提供します。さらに依存するプロジェクトを PyPI からフェッチ・インストールするスクリプト easy_install
も Setuptools
から提供されます。現在ユーザーが PyPI を使うときにはモジュールレベルの依存関係は全く使われず、最初から Setuptools
の拡張機能を使います。しかしこの機能は Setuptools
が独自に追加したオプションなので、Distutils
と PyPI はこれを理解できません。Setuptools
は事実上の標準を作成し、問題のある設計を覆うハックとなりました。
easy_install
はプロジェクトのアーカイブをダウンロードし、プロジェクトの setup.py
を実行して必要なメタデータを取得します。全ての依存プロジェクトについてこれを行い、依存グラフはダウンロードの度に少しずつ構成されます。
新しいメタデータは PyPI を通してオンラインで取得できますが、それでも easy_install
は全てのアーカイブをダウンロードします。この理由は、前述の通り、メタデータがプラットフォームごとに異なる可能性があるにもかかわらず、PyPI で公開されるメタデータがアップロードするときに使われたプラットフォームに固有となっているためです。このようにプロジェクトとその依存プロジェクトをインストールできるのはとても便利な機能であり、90% の場合はこれで十分です。そのため Setuptools
は広く使われるようになりました。しかしそれでも、問題が無いわけではありません:
-
依存プロジェクトのインストールが失敗したときにロールバックができないので、システムが壊れた状態となる可能性がある。
-
依存グラフがインストール中にその場で作られるので、依存プロジェクトの衝突があった場合にもシステムが壊れた状態となる可能性がある。
アンインストールの問題
インストールされたファイルのリストを Setuptools
のメタデータに持たせることは可能であったにもかかわらず、Setuptools
はアンインストーラを提供しませんでした。これに対して Pip
は Setuptools
のメタデータを拡張してインストールされたファイルを記録するようにしているので、アンインストールが可能です。しかしこれは“またもう一つの”メタデータであり、結果として Python のインストール場所にインストールされたプロジェクトには最大で四種類のメタデータが含まれることになってしまいました:
-
Distutils
のegg-info
: 単一のメタデータファイル。 -
Setuptools
のegg-info
: メタデータおよびSetuptools
固有のオプションを含んだディレクトリ。 -
Pip
のegg-info
:Setuptools
のegg-info
を拡張したもの。 -
ホストのパッケージシステムが作成するファイル/ディレクトリ。
データファイルはどうなる?
Distutils
では、データファイルをシステム上の任意の位置にインストールできます。パッケージのデータファイルを次のように定義したとします:
setup(...,
packages=['mypkg'],
package_dir={'mypkg': 'src/mypkg'},
package_data={'mypkg': ['data/*.dat']},
)
すると mypkg
プロジェクト内の拡張子が .dat
であるファイルがディストリビューションに追加され、Python のインストール場所に Python モジュールと共にインストールされるようになります。
Python ディストリビューションの外側にインストールするデータファイルについては、アーカイブに保存したデータファイルを指定した場所に移動させる別のオプションがあります:
setup(...,
data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
('config', ['cfg/data.cfg']),
('/etc/init.d', ['init-script'])]
)
しかし OS のパッケージャからすると、このやりかたは最悪です。なぜなら:
-
データファイルがメタデータに含まれないので、パッケージャは
setup.py
を読まなければならない。プロジェクトのコードを探らなければならない場合もある。 -
ターゲットのシステムにコピーするデータファイルを決められるのが開発者だけなのはまずい。
-
データファイルのカテゴリが存在しない。画像であれ、
man
ページであれ、その他全てのファイルと同じ扱いを受ける。
データファイルを含むプロジェクトをパッケージャが再パッケージしようとした場合には、プラットフォームに合うよう setup.py
ファイルを編集する以外に選択肢がありません。つまり、コードを見ていってデータファイルを定義している全ての行を編集しなければなりません。ファイルの場所を決めるのが開発者だからです。Setuptools
や Pip
を使ってもこの問題は改善されません。
改良された標準
こんなわけで、パッケージング環境は複雑で分かりにくくなってしまいました。全てが単一の Python モジュールで行われ、メタデータは表現力不足であり、プロジェクトの内容を完全に記述する方法はありません。これを改善するために私たちが行っていることをこれから説明します。
メタデータ
最初のステップは Metadata
の標準化です。新しいメタデータは PEP 345 で定義され、次の要素が含まれます:
-
バージョンを定義するより洗練された方法。
-
プロジェクトレベルの依存関係。
-
プラットフォーム固有の値を静的に定義するための方法。
バージョン
新しいメタデータの目標は、Python プロジェクトに対して操作を行う全てのツールがプロジェクトを同じ方法で識別できるようにすることです。バージョンの指定方法について言えば、これは全てのツールが“1.0”が“1.1”の前に来ることを知っていることを意味します。これだけなら簡単ですが、プロジェクトが特殊なバージョン化スキームを持っている場合、問題はずっと難しくなります。
一貫したバージョン付けを保証する唯一の方法は、プロジェクトが従うべき標準を公開することです。私たちは昔ながらのシーケンスベースのスキームを選択しました。PEP 386 で定義されるフォーマットは以下です:
N.N[.N]+[{a|b|c|rc}N[.N]+][.postN][.devN]
ここで:
-
N は整数です。二つ以上の N をドットで繋げた文字列 (MAJOR.MINOR) が最初に付きます。
-
a, b, c, rc はそれぞれ アルファ (alpha)、 ベータ (beta), リリース候補 (release candidate) を示すマーカーです。このマーカーの後には整数が続きます。リリース候補を表すマーカーが二つあるのは、スキーマに Python との互換性を持たせたかったからです。c を使った方が分かりやすいでしょう。
-
“devN”は開発バージョンを示すマーカーです。
-
“postN”はポストリリースバージョンを示すマーカーです。
プロジェクトのリリースプロセスに応じて、リリースの間の中間バージョンを dev や post のマーカーで表すことができます。多くのプロジェクトで dev マーカーが使われています。
PEP 386 はこのスキームの上に厳密な順序を定義しています:
- alpha < beta < rc < final
- dev < {alpha, beta, rc, final} < post
順序の例を次に示します:
1.0a1 < 1.0a2.dev456 < 1.0a2 < 1.0a2.1.dev456
< 1.0a2.1 < 1.0b1.dev456 < 1.0b2 < 1.0b2.post345
< 1.0c1.dev456 < 1.0c1 < 1.0.dev456 < 1.0
< 1.0.post456.dev34 < 1.0.post456
このスキームの狙いは、パッケージングシステムが Python プロジェクトのバージョンを自身のスキームに変換しやすいようにすることです。PyPI に送信したプロジェクトが PEP 345 準拠のメタデータを持っていているにもかかわらずバージョン番号が PEP 386 に従っていない場合、PyPI はそのプロジェクトを拒否します。
依存関係の問題
PEP 345 は PEP 314 の Requires
, Provides
, Obsoletes
を置き換える新しいフィールドを三つ定義します。そのフィールドは Requires-Dist
, Provides-Dist
, Obsoletes-Dist
であり、メタデータの中で何度も使うことができます。
Requires-Dist
の各エントリーはディストリビューションが必要とする他の Distutils
プロジェクトの名前を表す文字列です。そのフォーマットは Distutils
のプロジェクト名 (Name
フィールド) と同じで、バージョン番号をカッコで囲んで付けることもできます。Distutils
プロジェクトの名前は PyPI で検索できる名前と対応し、バージョンの宣言は PEP 386 に従います。いくつか例を示します:
Requires-Dist: pkginfo
Requires-Dist: PasteDeploy
Requires-Dist: zope.interface (>3.5.0)
Provides-Dist
はプロジェクトの別名を定義するのに使います。これはプロジェクトが他のプロジェクトをマージするときに便利です。例えば ZODB プロジェクトは transaction
プロジェクトを含むので、メタデータに次のように記述します:
Provides-Dist: transaction
Obsoletes-Dist
は他のプロジェクトを obsolete なバージョンとして印を付けるのに使います:
Obsoletes-Dist: OldName
環境マーカー
環境マーカーはフィールドの後に続くセミコロンから始まるマーカーで、実行環境に関する条件を追加します。いくつか例を示します:
Requires-Dist: pywin32 (>1.0); sys.platform == 'win32'
Obsoletes-Dist: pywin31; sys.platform == 'win32'
Requires-Dist: foo (1,!=1.3); platform.machine == 'i386'
Requires-Dist: bar; python_version == '2.4' or python_version == '2.5'
Requires-External: libxslt; 'linux' in sys.platform
環境マーカーのための簡易言語は、Python プログラマー以外にも理解しやすいよう意図的に簡単にしてあります。使えるのは文字列に関する ==
と in
演算子 (およびその否定) による比較とブール演算子です。PEP 345 においてこのマーカーが使えるのは次のフィールドです:
Requires-Python
Requires-External
Requires-Dist
Provides-Dist
Obsoletes-Dist
Classifier
何がインストールされるのか?
全ての Python ツールに共通のインストールフォーマットは相互運用のために必須です。インストーラ A がプロジェクト Foo をインストールしたことを インストーラ B から検出できるようにするには、二つのインストーラはインストール済みのプロジェクトに関する同一のデータベースを共有・更新しなければなりません。
もちろん理想的なユーザーがシステム内で使用するインストーラは一つだけですが、それでも新しい機能があるインストーラに乗り換えてみることは考えられます。例えば出荷状態の Mac OS X には Setuptools
がインストールされており、ユーザーは自動的に easy_install
スクリプトを手にすることになります。しかしユーザーが新しいツールに乗り換えるときには、Setuptools
との後方互換性が必要です。
RPM のようなパッケージングシステムを持つプラットフォームで Python インストーラを使ったとき生じる別の問題もあります。こういったシステムではプロジェクトがインストールされていることをシステムに伝える方法が存在しないのです。さらに悪いことに、Python インストーラから中央パッケージングシステムにどうにかインストールを通知できたとしても、Python メタデータからシステムデータへのマッピングが必要になります。例えばプロジェクトの名前がシステムごとに変えられる可能性があります。この改名が起きる理由はいくつかありますが、最も良くあるのが名前の衝突です。つまり、Python とは関係ない他のプロジェクトが同じ名前で RPM に登録されている場合です。あるいは python
を接頭語に持つ名前がプラットフォームにおける命名規則に沿っていないこともあります。例えば foo-python
という名前のプロジェクトは、Fedora RPM において python-foo
と改名される可能性が高いです。
この問題を解決する一つの方法は、中央パッケージングシステムが管理するグローバルな Python のインストール場所はそのまま放置にして、その他の作業は独立した環境で行うというものです。Virtualenv
のようなツールはこれを行います。
いずれにせよ、Python のインストールフォーマットを統一する必要があります。Python プロジェクトをインストールする際には他のパッケージングシステムとの相互運用性が重要だからです。サードパーティのパッケージングシステムが新しい Python プロジェクトを自身のシステムに登録したときには、Python のインストール場所に配置するメタデータを正しく生成しなければなりません。こうすることでそのプロジェクトは Python のインストール場所に問い合わせを行う任意の API から利用可能になります。
こうしておけばメタデータのマッピング問題が対処可能になります。RPM はラップしている Python プロジェクトを知っており、Python レベルのメタデータを生成できるからです。例えば、RPM は python26-webob
が PyPI エコシステムにおいて WebOb
と呼ばれていることを知っています。
標準の話に戻りましょう。PEP 376 はインストールされたパッケージに関する標準を定義しており、そのフォーマットは Setuptools
と Pip
で使われているものと似ています。それは名前の最後に dist-info
が付いたディレクトリを作り、その中に次のファイルを作成するというものです:
-
METADATA
: メタデータ本体。PEP 345, PEP 314, PEP 241 で定義される。 -
RECORD
: インストールされたファイルのリスト。csv 風のフォーマット。 -
INSTALLER
: プロジェクトをインストールしたツールの名前。 -
REQUESTED
: このファイルが存在する場合、プロジェクトのインストールが明示的であった (依存パッケージとしてインストールされたのではない) ことを意味する。
このフォーマットが全てのツールに理解されれば、Python のプロジェクトを特定のインストーラや機能に頼ることなく管理できるようになります。加えて PEP 376 はメタデータをディレクトリとして定義しているので、新しいファイルを追加してメタデータを拡張するのも簡単です。実際、後述する新しいメタデータファイル RESOURCES
が将来追加される可能性があるのですが、この場合にも PEP 376 の変更は必要になっていません。いずれこのファイルが全てのツールにとって便利であると判明すれば、PEP に追加されることになります。
データファイルのアーキテクチャ
前述の通り、パッケージャは開発者によって書かれたコードを改変することなくインストール時のデータファイルの移動場所を変更できる必要があります。そして同時に、開発者はデータファイルを移動先の場所を気にすることなく利用できなければなりません。私たちの解決法はそう特別なものではありません: 間接参照です。
データファイルの仕様
あなたの MPTools
アプリケーションが設定ファイルを利用するとしましょう。開発者はそのファイルを Python パッケージに入れ、__file__
を使って読み込むかもしれません:
import os
here = os.path.dirname(__file__)
cfg = open(os.path.join(here, 'config', 'mopy.cfg'))
こうすると設定ファイルがコードと同じようにインストールされ、開発者は設定ファイルをコードと同じディレクトリに配置しなければならないことになります。つまり今の例では、config
というサブディレクトリに配置しなければなりません。
私たちが設計した新しいデータファイルのアーキテクチャでは、プロジェクトツリーを全てのファイルのルートとして扱い、そのツリーの中の任意のファイルへのアクセスを許可しています。プロジェクトツリーが Python パッケージ内にあってもそうでない単純なディレクトリにあっても同様です。データファイル用のディレクトリを作成すれば、pkgutil.open
を使ってアクセスできます:
import os
import pkgutil
#Open the file located in config/mopy.cfg in the MPTools project
cfg = pkgutil.open('MPTools', 'config/mopy.cfg')
pkgutil.open
はまずプロジェクトのメタデータを読み、RESOURCES
ファイルがあるかどうかを調べます。この中にはファイルの名前からシステム内のファイルへのマッピングが含まれます:
config/mopy.cfg {confdir}/{distribution.name}
ここで {confdir}
はシステムの設定ディレクトリを指す変数であり、{distribution.name}
にはメタデータで定義される Python プロジェクトの名前が代入されます。
このメタデータファイル RESOURCES
がインストール時に作られる限り、API は mopy.cfg
ファイルの場所を見つけて開発者に渡すことができます。config/mopy.cfg
はプロジェクトツリーからの相対パスなので開発時にも使うことができ、その場合にはプロジェクトのメタデータがその場で生成された上で pkgutil
の探索パスに追加されます。
データファイルの宣言
プロジェクトがデータファイルを利用するときには、ファイルの配置場所を setup.cfg
ファイルに記述します。このとき記述するのは (glob スタイルのパターン, ターゲット)
の組が並んだマッパーです。各パターンがプロジェクトツリーのいくつかのファイルを指し、ターゲットがインストールパスを指定します。このときターゲットには波括弧で囲った変数を使うこともできます。例えば MPTools
の setup.cfg
は次のように書けます:
[files]
resources =
config/mopy.cfg {confdir}/{application.name}/
images/*.jpg {datadir}/{application.name}/
sysconfig
モジュールは利用可能な変数のリストをドキュメントと共に提供し、各プラットフォームにおける変数のデフォルト値を設定します。例えば Linux における {confdir}
の値は /etc
です。そのためインストーラは上述のマッパーを sysconfig
と共に使うことで、ファイルを配置場所をインストール時に計算できます。その後インストーラは RESOURCES
ファイルを作成し、pkgutil
によるファイル探索の準備が整います。
PyPI の改良
PyPI は事実上の単一障害点であると前に説明しました。PEP 380 はこの問題に対処するもので、PyPI がダウンした場合に代替サーバーへフォールバックするためのミラーリングプロトコルを定義します。ここでの目標は世界中のコミュニティメンバーがミラーを立てられるようにすることです。
ミラーのリストは X.pypi.python.org
という形のホスト名のリストとなっており、X
は a
, b
, c
, ..., aa
, ab
,... です。a.pypi.python.org
がマスターサーバーで、ミラーは b から始まります。last.pypi.python.org
のCNAME レコードは最後のミラーのホスト名を指しているので、PyPI を使うクライアントは CNAME を見るだけでミラーのリストを取得できます。
例えば次の呼び出しは最後のミラーが h.pypi.python.org
であることを示しているので、このときの PyPI が (b から h までの) 6 個のミラーを持っていることが分かります:
>>> import socket
>>> socket.gethostbyname_ex('last.pypi.python.org')[0]
'h.pypi.python.org'
このプロトコルを使うと、クライアントはミラーの IP の場所を調べてリクエストを一番近いミラーにリダイレクトできます。またマスターサーバーやミラーがダウンしているときに次のミラーにフォールバックすることも可能です。このミラーリングプロトコル自身は単純な rsync よりも複雑になります。ダウンロード数を正確に測定したり、最小限のセキュリティを提供したりするためです。
同期
ミラーは中央サーバーとやり取りするデータの量を最小限にしなけばなりません。そのためミラーは PyPI のXML-RPC 呼び出しを使って changelog
を最初に必ず取得し、ミラーの最終変更日時から変更があったパッケージだけを再フェッチします。また各パッケージ P に対して、simple/P
と serversig/P
にあるドキュメントのコピーを必ず行います。
あるパッケージが中央サーバーで削除された場合には、そのパッケージと関連する全てのファイルを削除しなければなりません。パッケージファイルの変更の検出には、ファイルの ETag をキャッシュした上で If-None-Match
ヘッダーを使ってリクエストをスキップする方法が使われます。同期が終わると、ミラーは /last-modified
を現在時刻に設定します。
統計の伝播
いずれかのミラーからリリースをダウンロードすると、プロトコルはダウンロードが行われたという情報を最初にマスターの PyPI サーバーに伝え、それから他のミラーにも伝えます。こうすることで、人やツールに向けて PyPI で表示されるリリースのダウンロード数が全てのミラーの値を集計したものとなります。
統計は日ごとおよび週ごとに中央 PyPI サーバーの stats
ディレクトリに CSV ファイルとして保存されます。各ミラーは自身の統計を local-stats
ディレクトリに保存します。それぞれのファイルにはアーカイブのダウンロード数が保存され、それらがユーザーエージェントごとにグループ化されます。中央サーバーは統計を集計するために一日ごとにミラーを訪れ、ミラーの値をグローバルの stats
ディレクトリに組み入れます。そのためミラーは少なくとも一日に一回は /local-stats
を更新しなければなりません。
ミラーの認証
どんなミラーリングシステムでもそうですが、ミラーされたデータが本物であることをクライアントが検証したい場合があります。考えられる脅威としては次のようなものがあります:
-
中央インデックスが攻撃を受ける。
-
ミラーが改ざんされる。
-
中央インデックスとエンドユーザーの間、またはミラーとエンドユーザーの間で man-in-the-middle 攻撃を受ける。
最初の攻撃を検出するには、パッケージの作者がパッケージを PGP 鍵で署名し、ディストリビューションが信頼する作者からのものであるとユーザーが検証できるようにする必要があります。ミラーリングプロトコルによって対処できるのは二つ目の脅威だけですが、man-in-the-middle 攻撃を検出するための試みもいくつか行われています。
中央インデックスは /serverkey
という URL で DSA 鍵を提供しています。これは openssl dsa -pubout
3で生成したものです。この URL はミラーしてはならず、クライアントは公式の PyPI から serverkey
を直接フェッチするか、PyPI のクライアントソフトウェアに付属するコピーを必ず利用します。ただしミラーも鍵のロールオーバーを検出するために鍵のダウンロードを行います。
各パッケージのミラーされたシグネチャは /serversig/package
に保存されます。これは対応する URL /simple/package
の DSA シグネチャを DER 形式で表したもので、SHA-1 with DSA が使われます4。
ミラーを使うクライアントは次のようにしてパッケージを検証します:
-
/simple
ページをダウンロードし、その SHA-1 ハッシュを計算する。 -
そのハッシュの DSA シグネチャを計算する。
-
対応する
/serversig
をダウンロードし、その値をステップ 2 で計算した値と一バイトずつ比較する。 -
ミラーからダウンロードした全てのファイルについて、MD5 ハッシュを計算して (
/simple
ページと比較して) 検証する。
中央インデックスからダウンロードする場合は検証は不必要であり、計算負荷を避けるためにも行うべきではありません。
鍵は一年に一度程度の頻度で更新され、そのたびにミラーは /serversig
ページを全て再フェッチします。クライアントは新しいサーバー鍵の信頼できるコピーを使う必要がありますが、そのための一つの方法は https://pypi.python.org/serverkey からダウンロードするというものであり、その際にはクライアントで mon-in-the-middle 攻撃を検出できるよう SSL サーバー証明書を検証する必要があります。この証明書は認証局によって署名されます。
実装の詳細
前節で説明した改良点の多くは Distutils2
で実装されています。setup.py
ファイルはもう使われなくなり、プロジェクトは .ini
ファイルに似た setup.cfg
ファイルで完全に記述されます。こうすることでパッケージャは Python コードを読むことなくプロジェクトのインストールの動作を変更できるようになります。setup.cfg
の例を示します:
[metadata]
name = MPTools
version = 0.1
author = Tarek Ziade
author-email = tarek@mozilla.com
summary = Set of tools to build Mozilla Services apps
description-file = README
home-page = http://bitbucket.org/tarek/pypi2rpm
project-url: Repository, http://hg.mozilla.org/services/server-devtools
classifier = Development Status :: 3 - Alpha
License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)
[files]
packages =
mopytools
mopytools.tests
extra_files =
setup.py
README
build.py
_build.py
resources =
etc/mopytools.cfg {confdir}/mopytools
この設定ファイルを使って、Distutils2
は次のことを行います:
-
メタデータファイル
META-1.2
を生成する。このファイルは PyPI への登録など様々なことに利用される。 -
sdist
などのパッケージ管理コマンドを実行する。 -
Distutils2
ベースのプロジェクトをインストールする。
Distutils2
は他にも version
モジュールを通して VERSION
を実装しています。
INSTALL-DB
の実装は Python 3.3 で標準ライブラリに組み込まれ、pkgutil
モジュールに取り込まれます。現在でもこのモジュールは Distutils2
に含まれているので、すぐに使用できます。提供される API を使うと Python のインストール場所をブラウズでき、このモジュールはインストールされたプロジェクトを完全に知っています。
こういった API は次にあげる Distutils2
の便利な機能の基礎となります:
-
インストーラ/アンインストーラ。
-
インストールされたプロジェクトの依存グラフの表示。
教訓
PEP が全て
Python のパッケージングほどに多様で複雑なアーキテクチャの変更は、PEP プロセスによる標準の変更を通じて注意深く行わなければなりません。私の経験から言うと、新しい PEP の作成や既存の PEP の変更には一年程度の時間がかかります。
このプロセスの中でコミュニティが犯した誤りの一つが、PEP を変更する代わりに、メタデータと Python アプリケーションのインストール方法をいじることで問題を“解決”するツールを提供したことでした。
言い換えると、使ったツールが標準ライブラリの Distutils
なのか Setuptols
なのかによって、アプリケーションのインストール方法が異なってしまったのです。新しいツールを使うコミュニティの一部は問題を解決できましたが、他の人々にとっては問題が増えただけでした。例えば OS パッケージャはいくつもの Python 標準に対応する必要が生じました: つまり公式にドキュメントされた標準と、Setuptools
が使うデファクトスタンダードな標準です。
しかしその一方で、Setuptols
には現実的な規模 (コミュニティ全体) での実験を行う機会が与えられ、速いペースで改良が進み、貴重なフィードバックを得ることもできました。新しい PEP を書くときには何が成功して何が失敗したかについて確信がありましたが、これは他の方法を取っていたら不可能だったでしょう。つまり、もしとあるサードパーティツールが革新を起こして問題を解決しているのであれば、PEP の変更を検討する良い機会だということです。
標準ライブラリに入ったパッケージは棺桶に片足を踏み入れる
これは Guido van Rossum の言葉を言い換えたものです。しかしこれは Python の“バッテリー搭載”哲学の一面であり、私たちの取り組みに様々な影響を及ぼしました。
Distutils
は標準ライブラリに組み込まれており、Distutils2
も近いうちにそうなります。標準ライブラリに組み込まれたパッケージは進化させるのが非常に難しくなります。もちろん廃止するためのプロセスは用意されており、Python のマイナーバージョン二つ先で API を廃止または変更できます。しかし API が一度公開されれば、通常は何年もそのままです。
そのため標準ライブラリに加えられるバグフィックスでない変更は、エコシステムにとって混乱の元になりかねません。重要な変更を行うには、新しいパッケージを作る必要があります。
私はこのことを Distutils
での苦労を通じて学びました。一年以上かけて取り組んできた変更を全て打ち消して、新しく Distutils2
を作らざるを得なかったのです。将来、標準がまた大きく変わるようなことがあれば、スタンドアローンの Distutils3
プロジェクトを最初に作る可能性が高いでしょう。標準ライブラリが別にリリースされるなら別ですが。
後方互換性
Python においてパッケージングの方式を変更するのはとても長い時間がかかります。Python エコシステムには古いパッケージングツールを使っているプロジェクトが大量にあるので、変更への抵抗が根強いからです (この章で説明した話題のいくつかについて合意に達するのには数年かかりました。私は数か月で済むと思っていたのですが)。Python 3 で全てのパッケージが新しい標準に移行するまでには数年はかかるでしょう。
そしてこのために、私たちは何をするときにも後方互換性を保たねばならないのです。これまでのツール、これまでのインストール方法、これまでの標準との後方互換性により、Distutils2
の実装は厄介な問題となります。
例えばあるプロジェクトが新しい標準を使っていて、その依存先のプロジェクトがまだ使っていなかったとしても、インストールを中止して「依存プロジェクトが不明なフォーマットを持っています!」とエンドユーザーに伝えるわけにはいきません。
もう一つ例をあげると、INSTALL-DB
の実装にはオリジナルの Distutils
, Pip
, Distribute
, Setuptools
でインストールされたプロジェクトを閲覧するための互換性コードがあります。また Distutils2
には Distutils
で作られたプロジェクトのメタデータを変換してインストールする機能があります。
参考文献と謝辞
この章のいくつかの節は、私たちがパッケージングシステムのために書いた様々な PEP ドキュメントからの直接の引用です。原文は http://python.org から読むことができます。
- PEP 241: Metadata for Python Software Packages 1.0: http://python.org/peps/pep-0214.html
- PEP 314: Metadata for Python Software Packages 1.1: http://python.org/peps/pep-0314.html
- PEP 345: Metadata for Python Software Packages 1.2: http://python.org/peps/pep-0345.html
- PEP 376: Database of Installed Python Distributions: http://python.org/peps/pep-0376.html
- PEP 381: Mirroring infrastructure for PyPI: http://python.org/peps/pep-0381.html
- PEP 386: Changing the version comparison module in Distutils: http://python.org/peps/pep-0386.html
パッケージングについて取り組んでいる全ての人に感謝します。彼らの名前は上述の PEP に載っています。「パッケージング同盟」のメンバーにも特別な感謝を送ります。またこの章に対するフィードバックをくれた Alexis Metaireau, Toshio Kuratomi, Holger Krekel, Stefane Fermigier にも感謝します。
この章で触れたプロジェクトを次にまとめます:
-
Distutils
: http://docs.python.org/distutils -
Distutils2
: http://packages.python.org/Distutils2 -
Distribute
: http://packages.python.org/distribute -
Setuptools
: http://pypi.python.org/pypi/setuptools -
Pip
: http://pypi.python.org/pypi/pip -
Virtualenv
: http://pypi.python.org/pypi/virtualenv