Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 518 – Python プロジェクトの最小ビルドシステム要件の指定

Author:
Brett Cannon <brett at python.org>, Nathaniel J. Smith <njs at pobox.com>, Donald Stufft <donald at stufft.io>
BDFL-Delegate:
Alyssa Coghlan
Discussions-To:
Distutils-SIG list
Status:
Final
Type:
Standards Track
Topic:
Packaging
Created:
10-May-2016
Post-History:
10-May-2016, 11-May-2016, 13-May-2016
Resolution:
Distutils-SIG message

Table of Contents

概要

この PEP は、Python ソフトウェアパッケージが選択したビルドシステムを実行するために必要なビルド依存関係を指定する方法を示しています。この仕様の一環として、ソフトウェアパッケージがビルド依存関係を指定するために使用する新しい構成ファイルが導入されます(将来的には同じ構成ファイルが他の構成詳細にも使用されることが期待されます)。

根拠

Python がプロジェクトのソフトウェアのディストリビューションをビルドするためのツールを最初に開発したとき、distutils [1] が選ばれたソリューションでした。時間が経つにつれて、setuptools [2] は distutils の上にいくつかの機能を追加するために人気を集めました。どちらも、プロジェクトメンテナーがソフトウェアのディストリビューションをビルドするために実行する setup.py ファイルの概念を使用しました(およびユーザーがそのディストリビューションをインストールするために使用しました)。

distutils の下でビルド要件を指定するために実行可能ファイルを使用することは問題ではありません。なぜなら、distutils は Python の標準ライブラリの一部だからです。ビルドツールが Python の一部であるため、setup.py にはプロジェクトメンテナーがビルドするために心配する必要のある外部依存関係はありません。唯一の依存関係は Python であるため、依存関係情報を指定する必要はありませんでした。

しかし、プロジェクトが setuptools を使用することを選択した場合、setup.py のような実行可能ファイルの使用は問題になります。依存関係を知らずに setup.py ファイルを実行することはできませんが、現在のところ、その依存関係を自動化された方法で知る標準的な方法はありません。依存関係情報が保存されている setup.py ファイルを実行しない限り、その内容をプログラム的に知ることはできません。これは、ファイルの内容を知らずに実行できないというキャッチ-22 の問題です。

Setuptools は、setup() 関数に setup_requires 引数を追加することでこれを解決しようとしました [3]。この解決策にはいくつかの問題があります。例えば:

  • setuptools 自体を除くツールは、この情報にアクセスするために setup.py を実行する必要がありますが、setup.py を実行するにはこれらの項目がインストールされている必要があります。
  • setuptools 自体はこれにリストされたものをインストールしますが、それらは setup() 関数の実行中にインストールされるため、ここに追加されたものを実際に使用する唯一の方法は、setup() 関数の実行中にこれらのモジュールのインポートと使用を遅らせる複雑な操作を通じて行うことです。
  • これには setuptools 自体や setuptools の代替品を含めることはできません。これにより、numpy.distutils などのプロジェクトはこれを利用することがほとんどできず、プロジェクトはユーザーが自然に setuptools のバージョンを新しいものにアップグレードするまで、新しい setuptools 機能を利用することができません。
  • setup_requires にリストされた項目は、setup.py を実行するたびに暗黙的にインストールされますが、setup.py が実行される一般的な方法の1つは、pip などの他のツールを介して行われます。これにより、pip install spam のようなコマンドが、pip と setuptools の両方がパッケージをダウンロードしてインストールすることになり、エンドユーザーが両方のツールを設定する必要が生じます(setuptools の場合は呼び出しを制御せずに)。これにより、ユーザーは両方のツールの発見ルールを認識する必要があります。1つのツールは異なるパッケージ形式をサポートしているか、最新バージョンを異なる方法で決定する場合があります。

これにより、setup_requires の使用はまれであり、プロジェクトは setup.py ファイル間でスニペットをコピーして貼り付けるか、ビルドまたはインストールを試みる前にユーザーが手動でインストールすることを期待するものを他の場所に文書化することを選択する傾向があります。

これにより、pip [4]setup.py ファイルを実行する際に setuptools が必要であると単純に仮定するようになりました。しかし、これには問題があります。コミュニティで setuptools と同じように人気を集める別のプロジェクトが登場した場合、スケールしません。また、pip が setuptools 以外のものが必要であることを推測できないため、他のプロジェクトが普及するのを妨げます。

この PEP は、プロジェクトのビルドシステムの最小依存関係を特定のファイルに宣言的にリストする方法を指定することで、この状況を修正しようとしています。これにより、プロジェクトがソースチェックアウトからホイールに移行するために必要なビルド依存関係をリストすることができ、setup.py が持つツールがプロジェクトが自分自身をビルドするために何が必要かを推測できないというキャッチ-22 の罠に陥ることはありません。この PEP を実装することで、プロジェクトが依存するビルドシステムを事前に指定できるようになり、pip などのツールがそれらをインストールしてビルドシステムを実行し、プロジェクトをビルドできるようになります。

この PEP の背景と動機を提供するために、プロジェクトのビルドアーティファクトを生成するために必要な(大まかな)手順を考えてみてください。

  1. プロジェクトのソースチェックアウト。
  2. ビルドシステムのインストール。
  3. ビルドシステムの実行。

この PEP はステップ2をカバーしています。PEP 517 はステップ3をカバーしており、ビルドシステムがその役割を果たすために必要な依存関係を動的に指定する方法を含んでいます。しかし、この PEP の目的は、ビルドシステムが単に実行を開始するための最小要件を指定することです。

仕様

ファイル形式

ビルドシステムの依存関係は、TOML 形式 [6] で記述された pyproject.toml という名前のファイルに保存されます。

この形式は、人間が使用しやすい(JSON [7] とは異なり)、柔軟性がある(configparser [9] とは異なり)、標準に基づいている(configparser [9] とは異なり)、複雑すぎない(YAML [8] とは異なり)ために選ばれました。TOML 形式は、Rust コミュニティによって Cargo パッケージマネージャー [14] の一部としてすでに使用されており、彼らは TOML の選択に非常に満足していると述べています。さまざまな代替案が選ばれなかった理由についての詳細な議論は、他のファイル形式 セクションで読むことができます。著者は、構成ファイル形式の選択は最終的には主観的であり、選択を行う必要があることを認識していますが、この状況では TOML を好みます。

以下に、ツールが認識/尊重することが期待されるテーブルをリストします。この PEP で指定されていないテーブルは、他の PEP による将来の使用のために予約されています。

build-system テーブル

[build-system] テーブルは、ビルドに関連するデータを保存するために使用されます。最初は、このテーブルの1つのキーのみが有効であり、必須です:requires。このキーは、ビルドシステムを実行するために必要な PEP 508 依存関係を表す文字列のリストの値を持つ必要があります(現在は setup.py ファイルを実行するために必要な依存関係を意味します)。

setuptools に依存する大多数の Python プロジェクトでは、pyproject.toml ファイルは次のようになります:

[build-system]
# ビルドシステムを実行するための最小要件。
requires = ["setuptools", "wheel"]  # PEP 508 仕様。

現在コミュニティで setuptools と wheel の使用が非常に広範であるため、ビルドツールは上記の例の構成ファイルを pyproject.toml ファイルが存在しない場合のデフォルトのセマンティクスとして使用することが期待されます。

ツールは [build-system] テーブルの存在を要求してはなりません。pyproject.toml ファイルは、ビルドに関連するデータ以外の構成詳細を保存するために使用される場合があり、正当な理由で [build-system] テーブルが欠けていることがあります。ファイルが存在するが [build-system] テーブルが欠けている場合は、上記で指定されたデフォルト値を使用する必要があります。テーブルが指定されているが、必須フィールドが欠けている場合は、ツールはそれをエラーと見なすべきです。

tool テーブル

[tool] テーブルは、ビルドツールに限らず、Python プロジェクトに関連する任意のツールが、[tool] 内のサブテーブルを使用してユーザーが構成データを指定できる場所です。例えば、flit ツールは、[tool.flit] に構成を保存します。

tool.* 名前空間内の名前を割り当てるためのメカニズムが必要です。異なるプロジェクトが同じサブテーブルを使用しようとして衝突するのを防ぐためです。私たちのルールは、プロジェクトが Cheeseshop/PyPI に $NAME のエントリを所有している場合にのみ、tool.$NAME サブテーブルを使用できるというものです。

JSON スキーマ

TOML ファイルからの結果データの型固有の表現を提供するために、以下の JSON スキーマ [15] がデータ形式に一致します:

{
    "$schema": "http://json-schema.org/schema#",

    "type": "object",
    "additionalProperties": false,

    "properties": {
        "build-system": {
            "type": "object",
            "additionalProperties": false,

            "properties": {
                "requires": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            },
            "required": ["requires"]
        },

        "tool": {
            "type": "object"
        }
    }
}

却下されたアイデア

セマンティックバージョンキー

構成ファイルの構造を将来にわたって保護するために、semantics-version キーが最初に提案されました。デフォルトは 1 で、以前に定義されたキーやテーブルのセマンティクスが後方互換性のない変更を受けた場合に、semantics-version が新しい番号にインクリメントされるというアイデアです。

しかし、最終的には、これは時期尚早の最適化であると判断されました。構成ファイルで事前に定義されたセマンティクスの変更は、かなり保守的であると予想されます。そして、後方互換性のない変更が発生する場合には、新しいセマンティクスのために異なる名前を使用して、古いツールを壊さないようにすることができます。

よりネストされた名前空間

この PEP の初期のドラフトには、トップレベルの [package] テーブルがありました。これは、セマンティックバージョニングスキームのスコープを設定するためのアイデアでした(なぜそのアイデアが却下されたかについては、セマンティックバージョンキー を参照してください)。スコープの必要性がなくなったため、トップレベルのテーブルを持つことの意味がなくなりました。

他のテーブル名

[build-system] テーブルの別の名前として [build] が提案されました。代替名は短いですが、テーブルに保存される情報の意図をあまり伝えません。distutils-sig メーリングリストでの投票の結果、現在の名前が選ばれました。

他のファイル形式

いくつかの他のファイル形式が検討されましたが、さまざまな理由で却下されました。主な要件は、形式が人間によって編集可能であり、プロジェクトによって簡単にベンダリングできる実装があることです。これにより、XML のような形式は人間にとって使いやすくないため、真剣に議論されることはありませんでした。

検討されたファイル形式の概要

他の代替案が却下された主な理由は、以下のセクションに要約されています。完全なレビュー(TOML を支持する肯定的な議論を含む)は [16] で見つけることができます。

最終的に TOML が選ばれたのは、私たちが興味を持っていたすべての機能を提供し、代替案によって導入される欠点を回避できたためです。

機能 TOML YAML JSON CFG/INI
明確に定義されている yes yes yes
実際のデータ型 yes yes yes
信頼性のある Unicode yes yes yes
信頼性のあるコメント yes yes
人間が編集しやすい yes ?? ??
ツールが編集しやすい yes ?? yes ??
標準ライブラリに含まれている yes yes
pip がベンダリングしやすい yes n/a n/a

(表の「??」は、多くの人が「yes」と答える傾向がある項目ですが、明確な仕様がないため、または基礎となるファイル形式の仕様が驚くほど複雑であるため、実際には多くの癖やエッジケースが発生することを示しています)

pytoml TOML パーサーは約300行の純粋な Python コードであるため、標準ライブラリの外にあることは大きな問題ではありませんでした。

Python リテラルも潜在的な形式として議論されましたが、一般的な既存のファイル形式ではないため、ファイル形式のレビューには含まれていませんでした。

JSON

JSON 形式 [7] は最初に検討されましたが、すぐに却下されました。文字列ベースのデータ交換形式としては優れていますが、構文が人間による編集に適していません(例:構文が必要以上に冗長であり、コメントを許可しません)。

提案されたデータの JSON ファイルの例は次のとおりです:

{
    "build": {
        "requires": [
            "setuptools",
            "wheel>=0.27"
        ]
    }
}

YAML

YAML 形式 [8] は、手作業で扱いやすくするために JSON [7] のスーパーセットとして設計されました。YAML には3つの主な問題があります。

1つは、仕様が大きいことです。レターサイズの紙に印刷すると86ページになります。これにより、あるパーサーで動作する YAML の機能が他のパーサーでは動作しない可能性があります。サブセットを標準化することが提案されましたが、これはこのファイルに特化した新しい標準を作成することを意味し、長期的には実行可能ではありません。

2つ目は、YAML 自体がデフォルトで安全ではないことです。仕様では、任意のコードの実行を許可しており、構成データを扱う際には避けるべきです。もちろん、この動作を回避することは可能です。たとえば、PyYAML は safe_load 操作を提供していますが、ツールが不注意に load を使用すると、任意のコード実行のリスクがあります。この PEP はプロジェクトのビルドに焦点を当てており、ビルドにはコードの実行が含まれますが、プロジェクト名やバージョン番号などの他の構成データが将来的に同じファイルに含まれる可能性があり、その場合には任意のコード実行は望ましくありません。

最後に、YAML の最も人気のある Python 実装は PyYAML [10] であり、数千行のコードとオプションの C 拡張モジュールを持つ大規模なプロジェクトです。これ自体は必ずしも問題ではありませんが、pip のようなプロジェクトにとっては、依存関係として PyYAML をベンダリングする必要があるため、完全に自己完結型になる必要があります(そうしないと、インストールツールが動作するためにインストールツールが必要になります)。ライブラリの簡素化バージョンをベンダリングする可能性を示すために、PyYAML の概念実証の再作業が行われました。

YAML ファイルの例は次のとおりです:

build:
    requires:
        - setuptools
        - wheel>=0.27

configparser

configparser [9] が受け入れる INI スタイルの構成ファイルが検討されました。残念ながら、configparser が受け入れるものの仕様はなく、バージョン間でサポートの偏りがあります。たとえば、Python 2.7 の ConfigParser が受け入れるものは、Python 3 の configparser が受け入れるものとは異なります。Python 3 が受け入れるものを標準化し、configparser モジュールのバックポートをベンダリングすることはできますが、これにより、この PEP で指定されたメタデータを消費するすべてのプロジェクトが特定のバージョンの configparser を使用する必要があることを明示的に規定する必要があります。これは過度に制限的であり、特定のバージョンの configparser が期待されることを認識していない場合に混乱を招く可能性があります。

INI ファイルの例は次のとおりです:

[build]
requires =
    setuptools
    wheel>=0.27

Python リテラル

Python リテラルを構成形式として使用することが提案されました。ファイルにはトップレベルに1つの辞書が含まれ、その辞書内にすべてのデータが含まれ、セクションはキーによって定義されます。すべての Python プログラマーは形式に慣れており、構成データを読み取るためにサードパーティの依存関係が必要なく、ast.literal_eval() [13] で解析することで安全にすることができます。Python リテラルは JSON と同一であり、末尾のカンマやコメントをサポートする追加の利点があります。さらに、Python のより豊富なデータモデルは、将来の構成ニーズに役立つかもしれません(例:非文字列の辞書キー、浮動小数点数と整数の値の区別)。

一方で、Python リテラルは Python 固有の形式であり、これらのデータは Python で書かれていないパッケージングツールなどによって読み取られる必要があると予想されます。

提案されたデータの Python リテラルファイルの例は次のとおりです:

# ビルド構成
{"build": {"requires": ["setuptools",
                        "wheel>=0.27", # 末尾のカンマに注意
                        # "numpy>=1.10" # コメントアウトされたデータ行
                        ]
# ここに任意のコメントがあります。
           }
 }

setup.cfg を使用し続ける

setuptools によって一般的な形式として使用される setup.cfg には2つの問題があります。1つは、configparser の議論で述べたように、.ini ファイルであることです。もう1つは、そのファイルのスキーマが厳密に定義されたことがなく、将来的にどの形式が安全に使用できるかが不明であり、setuptools のインストールを混乱させる可能性があることです。

他のファイル名

いくつかの他のファイル名が検討され、却下されました(これは非常にバイクシェディングなトピックであり、決定は主に好みによるものです)。

pysettings.toml
最も合理的な代替案。
pypa.toml
PyPA [11] を参照するのは理にかなっていますが、ややニッチな用語です。ドメイン固有の知識がなくてもファイル名が意味を持つ方が良いです。
pybuild.toml
この PEP の制限的な視点からはこのファイル名は理にかなっていますが、将来的にビルド以外のメタデータがファイルに追加されると、名前が意味をなさなくなります。
pip.toml
ツール固有すぎます。
meta.toml
あまりにも一般的です。プロジェクトは独自のメタデータファイルを持ちたいかもしれません。
setup.toml
setup.py に感謝しつつも、将来的にファイルに含まれる可能性のある内容と一致しないかもしれません(例:プロジェクトの名前を知ることは本質的にそのセットアップの一部ですか?)。
pymeta.toml
プログラミングや Python に不慣れな人には明らかではありません。
pypackage.toml & pypackaging.toml
「パッケージ」という名前の混同(プロジェクトと名前空間の違い)。
pydevelop.toml
ファイルには開発に特化しない詳細が含まれる可能性があります。
pysource.toml
ソースコードに直接関連していません。
pytools.toml
ファイルは(現在)プロジェクト管理を目的としているため、誤解を招きます。
dstufft.toml
個人固有すぎます。 ;)

参考文献

著作権

このドキュメントはパブリックドメインに置かれています。


Source: https://github.com/python/peps/blob/main/peps/pep-0518.rst

Last modified: 2024-10-22 05:20:14 GMT