PEP 518 – Angabe von Mindestanforderungen an das Build-System für Python-Projekte
- Autor:
- 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 Liste
- Status:
- Final
- Typ:
- Standards Track
- Thema:
- Packaging
- Erstellt:
- 10. Mai 2016
- Post-History:
- 10. Mai 2016, 11. Mai 2016, 13. Mai 2016
- Resolution:
- Distutils-SIG Nachricht
Inhaltsverzeichnis
Zusammenfassung
Dieses PEP spezifiziert, wie Python-Softwarepakete ihre Build-Abhängigkeiten angeben sollen, um ihr gewähltes Build-System auszuführen. Als Teil dieser Spezifikation wird eine neue Konfigurationsdatei für Softwarepakete eingeführt, die zur Angabe ihrer Build-Abhängigkeiten verwendet wird (in Erwartung, dass dieselbe Konfigurationsdatei für zukünftige Konfigurationsdetails verwendet wird).
Begründung
Als Python seine Werkzeuge für die Erstellung von Software-Distributionen für Projekte entwickelte, war distutils [1] die gewählte Lösung. Mit der Zeit gewann setuptools [2] an Popularität, um einige Funktionen auf distutils aufzubauen. Beide nutzten das Konzept einer setup.py-Datei, die Projektbetreuer zum Erstellen von Software-Distributionen ausführten (ebenso wie Benutzer zur Installation besagter Distribution).
Die Verwendung einer ausführbaren Datei zur Angabe von Build-Anforderungen unter distutils ist kein Problem, da distutils Teil der Python-Standardbibliothek ist. Da das Build-Tool Teil von Python ist, hat eine setup.py keine externe Abhängigkeit, um die sich ein Projektbetreuer beim Erstellen einer Distribution seines Projekts sorgen muss. Es war keine Notwendigkeit, Abhängigkeitsinformationen anzugeben, da die einzige Abhängigkeit Python ist.
Aber wenn ein Projekt sich entscheidet, setuptools zu verwenden, wird die Verwendung einer ausführbaren Datei wie setup.py zu einem Problem. Sie können eine setup.py-Datei nicht ausführen, ohne ihre Abhängigkeiten zu kennen, aber derzeit gibt es keine standardisierte Möglichkeit, diese Abhängigkeiten automatisiert zu ermitteln, ohne die setup.py-Datei auszuführen, in der diese Informationen gespeichert sind. Es ist ein Henne-Ei-Problem, da die Datei ohne Kenntnis ihres eigenen Inhalts nicht ausführbar ist, der programmatisch nicht ermittelt werden kann, es sei denn, die Datei wird ausgeführt.
Setuptools versuchte dies mit dem Argument setup_requires für seine setup()-Funktion [3] zu lösen. Diese Lösung hat eine Reihe von Problemen, wie zum Beispiel:
- Keine Werkzeuge (außer setuptools selbst) können auf diese Informationen zugreifen, ohne die
setup.pyauszuführen, abersetup.pykann nicht ausgeführt werden, ohne dass diese Elemente installiert sind. - Obwohl setuptools selbst alles auflistet, wird dies erst *während* der Ausführung der
setup()-Funktion installiert. Das bedeutet, dass die einzige Möglichkeit, etwas, das hier hinzugefügt wird, tatsächlich zu nutzen, über zunehmend komplexe Machenschaften erfolgt, die den Import und die Verwendung dieser Module bis später in der Ausführung dersetup()-Funktion verzögern. - Dies kann weder
setuptoolsselbst noch einen Ersatz fürsetuptoolsenthalten, was bedeutet, dass Projekte wienumpy.distutilssie weitgehend nicht nutzen können und Projekte neuere setuptools-Funktionen nicht nutzen können, bis ihre Benutzer die Version von setuptools auf eine neuere aktualisieren. - Die in
setup_requiresaufgeführten Elemente werden implizit installiert, wann immer Sie diesetup.pyausführen. Aber eine der gängigen Methoden zur Ausführung vonsetup.pyist über ein anderes Werkzeug, wie z.B.pip, das bereits Abhängigkeiten verwaltet. Das bedeutet, dass ein Befehl wiepip install spamdazu führen kann, dass sowohl pip als auch setuptools Pakete herunterladen und installieren, und Endbenutzer müssen *beide* Werkzeuge konfigurieren (und fürsetuptoolsohne Kontrolle über die Aufrufung), um Einstellungen wie das Repository, von dem installiert wird, zu ändern. Es bedeutet auch, dass Benutzer die Entdeckungsregeln für beide Werkzeuge kennen müssen, da eines möglicherweise andere Paketformate unterstützt oder die neueste Version anders ermittelt.
Dies hat zu einer Situation geführt, in der die Verwendung von setup_requires selten ist, wobei Projekte dazu neigen, entweder einfach Codeausschnitte zwischen setup.py-Dateien zu kopieren und einzufügen, oder sie verzichten ganz darauf und dokumentieren stattdessen woanders, was sie vom Benutzer erwarten, bevor sie versuchen, ihr Projekt zu erstellen oder zu installieren.
All dies hat dazu geführt, dass pip [4] einfach davon ausgeht, dass setuptools für die Ausführung einer setup.py-Datei notwendig ist. Das Problem dabei ist jedoch, dass es nicht skaliert, wenn ein anderes Projekt in der Community an Bedeutung gewinnt, so wie es setuptools getan hat. Es hindert auch andere Projekte daran, an Bedeutung zu gewinnen, aufgrund der Reibung, die erforderlich ist, um es mit einem Projekt zu verwenden, wenn pip nicht erkennen kann, dass etwas anderes als setuptools benötigt wird.
Dieses PEP versucht, die Situation zu korrigieren, indem es eine Möglichkeit spezifiziert, die minimalen Abhängigkeiten des Build-Systems eines Projekts deklarativ in einer bestimmten Datei aufzulisten. Dies ermöglicht es einem Projekt, seine Build-Abhängigkeiten anzugeben, um z.B. von einem Quellcode-Checkout zu einem Wheel zu gelangen, ohne in die Henne-Ei-Falle einer setup.py zu geraten, bei der Werkzeuge nicht ermitteln können, was ein Projekt zum Erstellen benötigt. Die Implementierung dieses PEP wird es Projekten ermöglichen, anzugeben, von welchem Build-System sie vorab abhängen, damit Werkzeuge wie pip sicherstellen können, dass diese installiert sind, um das Build-System zur Erstellung des Projekts auszuführen.
Um mehr Kontext und Motivation für dieses PEP zu geben, denken Sie an die (ungefähren) Schritte, die erforderlich sind, um ein gebautes Artefakt für ein Projekt zu erstellen:
- Der Quellcode-Checkout des Projekts.
- Installation des Build-Systems.
- Ausführung des Build-Systems.
Dieses PEP deckt Schritt #2 ab. PEP 517 deckt Schritt #3 ab, einschließlich der Art und Weise, wie das Build-System dynamisch weitere Abhängigkeiten angeben kann, die das Build-System zur Ausführung seiner Aufgabe benötigt. Der Zweck dieses PEP ist jedoch die Angabe des minimalen Satzes von Anforderungen, damit das Build-System einfach mit der Ausführung beginnen kann.
Spezifikation
Dateiformat
Die Build-System-Abhängigkeiten werden in einer Datei namens pyproject.toml gespeichert, die im TOML-Format [6] geschrieben ist.
Dieses Format wurde gewählt, da es für Menschen nutzbar ist (im Gegensatz zu JSON [7]), es flexibel genug ist (im Gegensatz zu configparser [9]), auf einem Standard basiert (auch im Gegensatz zu configparser [9]) und nicht übermäßig komplex ist (im Gegensatz zu YAML [8]). Das TOML-Format wird bereits von der Rust-Community als Teil ihres Cargo-Paketmanagers verwendet [14] und sie äußerten in privaten E-Mails, dass sie mit ihrer Wahl von TOML recht zufrieden waren. Eine ausführlichere Diskussion darüber, warum verschiedene Alternativen nicht gewählt wurden, finden Sie im Abschnitt Andere Dateiformate. Die Autoren erkennen jedoch an, dass die Wahl des Konfigurationsdateiformats letztlich subjektiv ist und eine Entscheidung getroffen werden musste, und die Autoren bevorzugen TOML für diese Situation.
Im Folgenden listen wir die Tabellen auf, die Werkzeuge erkennen/respektieren sollen. Tabellen, die in diesem PEP nicht spezifiziert sind, sind für die zukünftige Verwendung durch andere PEPs reserviert.
Tabelle build-system
Die Tabelle [build-system] wird zur Speicherung von Build-bezogenen Daten verwendet. Anfänglich ist nur ein Schlüssel dieser Tabelle gültig und für die Tabelle zwingend erforderlich: requires. Dieser Schlüssel muss einen Wert haben, der eine Liste von Zeichenketten ist, die PEP 508-Abhängigkeiten repräsentieren, die zur Ausführung des Build-Systems erforderlich sind (derzeit bedeutet das, welche Abhängigkeiten zur Ausführung einer setup.py-Datei erforderlich sind).
Für die überwiegende Mehrheit der Python-Projekte, die auf setuptools angewiesen sind, wird die Datei pyproject.toml wie folgt aussehen:
[build-system]
# Minimum requirements for the build system to execute.
requires = ["setuptools"] # PEP 508 specifications.
Da die Verwendung von setuptools in der Community derzeit so weit verbreitet ist, wird erwartet, dass Build-Werkzeuge die obige Beispielkonfigurationsdatei als Standardsemantik verwenden, wenn keine pyproject.toml-Datei vorhanden ist.
Werkzeuge sollten die Existenz der Tabelle [build-system] nicht erzwingen. Eine pyproject.toml-Datei kann verwendet werden, um andere Konfigurationsdetails als Build-bezogene Daten zu speichern, und daher legitim eine [build-system]-Tabelle fehlen lassen. Wenn die Datei existiert, aber die Tabelle [build-system] fehlt, sollten die oben spezifizierten Standardwerte verwendet werden. Wenn die Tabelle angegeben ist, aber erforderliche Felder fehlen, sollte das Werkzeug dies als Fehler betrachten.
Tabelle tool
Die Tabelle [tool] ist der Ort, an dem jedes Werkzeug, das mit Ihrem Python-Projekt zusammenhängt, nicht nur Build-Werkzeuge, vom Benutzer Konfigurationsdaten angeben lassen kann, solange es eine Untertabelle innerhalb von [tool] verwendet, z.B. das flit-Werkzeug würde seine Konfiguration in [tool.flit] speichern.
Wir benötigen einen Mechanismus zur Zuweisung von Namen im Namensraum tool.*, um sicherzustellen, dass verschiedene Projekte nicht versuchen, dieselbe Untertabelle zu verwenden und zu kollidieren. Unsere Regel ist, dass ein Projekt die Untertabelle tool.$NAME verwenden kann, wenn und nur wenn es den Eintrag für $NAME im Cheeseshop/PyPI besitzt.
JSON-Schema
Um eine typenspezifische Darstellung der aus der TOML-Datei resultierenden Daten zu Illustrationszwecken bereitzustellen, würde das folgende JSON-Schema [15] dem Datenformat entsprechen:
{
"$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"
}
}
}
Abgelehnte Ideen
Ein semantischer Versionsschlüssel
Zur Zukunftssicherheit der Struktur der Konfigurationsdatei wurde zunächst ein Schlüssel semantics-version vorgeschlagen. Standardmäßig auf 1 gesetzt, war die Idee, dass, wenn sich die Semantik von zuvor definierten Schlüsseln oder Tabellen rückwärtsinkompatibel ändert, die semantics-version auf eine neue Nummer erhöht würde.
Letztendlich wurde jedoch entschieden, dass dies eine voreilige Optimierung war. Die Erwartung ist, dass Änderungen an dem, was semantisch in der Konfigurationsdatei vordefiniert ist, eher konservativ sein werden. Und in Fällen, in denen eine rückwärtsinkompatible Änderung aufgetreten wäre, können neue Namen für die neuen Semantiken verwendet werden, um ältere Werkzeuge nicht zu brechen.
Ein verschachtelterer Namensraum
Ein früherer Entwurf dieses PEP hatte eine [package]-Tabelle auf oberster Ebene. Die Idee war, eine gewisse Abgrenzung für ein Semantik-Versionierungsschema zu erzwingen (siehe Ein semantischer Versionsschlüssel, warum diese Idee abgelehnt wurde). Mit dem Wegfall der Notwendigkeit einer Abgrenzung wurde der Sinn einer Tabelle auf oberster Ebene überflüssig.
Andere Tabellennamen
Ein anderer Name, der für die Tabelle [build-system] vorgeschlagen wurde, war [build]. Der alternative Name ist kürzer, vermittelt aber nicht die Absicht, welche Informationen in der Tabelle gespeichert sind. Nach einer Abstimmung in der Mailingliste distutils-sig setzte sich der aktuelle Name durch.
Andere Dateiformate
Mehrere andere Dateiformate wurden zur Prüfung vorgelegt, alle aus verschiedenen Gründen abgelehnt. Wichtige Anforderungen waren, dass das Format von Menschen bearbeitet werden kann und eine Implementierung hat, die von Projekten leicht mitgeliefert werden kann. Dies schloss Formate wie XML, die für Menschen nicht benutzerfreundlich sind, von vornherein aus und wurden nie ernsthaft diskutiert.
Übersicht der betrachteten Dateiformate
Die Hauptgründe für die Ablehnung der anderen betrachteten Alternativen sind in den folgenden Abschnitten zusammengefasst, während die vollständige Überprüfung (einschließlich positiver Argumente für TOML) im Abschnitt Andere Dateiformate zu finden ist. Die Autoren haben jedoch auch die volle Überprüfung (einschließlich positiver Argumente für TOML) im Abschnitt Dateiformate eingereicht.
TOML wurde letztendlich ausgewählt, da es alle gewünschten Funktionen bot und die Nachteile der Alternativen vermied.
| Merkmal | TOML | YAML | JSON | CFG/INI |
|---|---|---|---|---|
| Gut definiert | ja | ja | ja | |
| Echte Datentypen | ja | ja | ja | |
| Zuverlässiges Unicode | ja | ja | ja | |
| Zuverlässige Kommentare | ja | ja | ||
| Einfach für Menschen zu bearbeiten | ja | ?? | ?? | |
| Einfach für Werkzeuge zu bearbeiten | ja | ?? | ja | ?? |
| In Standardbibliothek | ja | ja | ||
| Einfach für pip mitzuliefern | ja | n. z. z. | n. z. z. |
(„??“ in der Tabelle kennzeichnet Elemente, bei denen die meisten Leute geneigt wären, mit „ja“ zu antworten, aber aufgrund mangelnder klarer Spezifikation oder einer überraschend komplizierten zugrunde liegenden Dateiformatspezifikation viele Tücken und Sonderfälle auftreten.)
Der pytoml TOML-Parser besteht aus ca. 300 Zeilen reinem Python-Code, daher hat es nicht stark gegen ihn gesprochen, nicht Teil der Standardbibliothek zu sein.
Python-Literale wurden ebenfalls als potenzielles Format diskutiert, aber nicht in der Dateiformatprüfung berücksichtigt (da es sich nicht um ein gängiges, bereits vorhandenes Dateiformat handelt).
JSON
Das JSON-Format [7] wurde anfänglich in Betracht gezogen, aber schnell verworfen. Obwohl es ein großartiges menschenlesbares, zeichenkettenbasiertes Datenaustauschformat ist, eignet sich die Syntax nicht zur einfachen Bearbeitung durch einen Menschen (z.B. ist die Syntax unnötig ausführlich, erlaubt aber keine Kommentare).
Eine Beispiel-JSON-Datei für die vorgeschlagenen Daten wäre:
{
"build": {
"requires": [
"setuptools",
"wheel>=0.27"
]
}
}
YAML
Das YAML-Format [8] wurde als Obermenge von JSON [7] konzipiert, während es einfacher von Hand zu bearbeiten ist. Es gibt drei Hauptprobleme mit YAML.
Erstens ist die Spezifikation sehr umfangreich: 86 Seiten auf DIN-A4-Papier gedruckt. Das lässt die Möglichkeit offen, dass jemand eine YAML-Funktion verwendet, die mit einem Parser funktioniert, aber nicht mit einem anderen. Es wurde vorgeschlagen, einen Teil davon zu standardisieren, aber das bedeutet im Grunde, einen neuen Standard speziell für diese Datei zu erstellen, was langfristig nicht praktikabel ist.
Zweitens ist YAML selbst standardmäßig nicht sicher. Die Spezifikation erlaubt die willkürliche Ausführung von Code, was am besten vermieden wird, wenn mit Konfigurationsdaten gearbeitet wird. Es ist natürlich möglich, dieses Verhalten zu vermeiden – zum Beispiel bietet PyYAML eine safe_load-Operation –, aber wenn ein Werkzeug versehentlich stattdessen load verwendet, öffnet es sich der willkürlichen Codeausführung. Während sich dieses PEP auf das Erstellen von Projekten konzentriert, was naturgemäß Codeausführung beinhaltet, könnten andere Konfigurationsdaten wie Projektname und Versionsnummer eines Tages in derselben Datei landen, wo die willkürliche Codeausführung nicht gewünscht ist.
Und schließlich ist die beliebteste Python-Implementierung von YAML PyYAML [10], ein großes Projekt mit einigen tausend Codezeilen und einem optionalen C-Erweiterungsmodul. Während dies an sich noch kein Problem darstellt, wird es zu einem größeren Problem für Projekte wie pip, die PyYAML wahrscheinlich als Abhängigkeit mitliefern müssten, um vollständig eigenständig zu sein (andernfalls benötigt Ihr Installationswerkzeug ein Installationswerkzeug, um zu funktionieren). Ein Proof-of-Concept-Neuentwurf von PyYAML wurde durchgeführt, um zu sehen, wie einfach es wäre, eine einfachere Version der Bibliothek mitzuliefern, was zeigt, dass es möglich ist.
Eine Beispiel-YAML-Datei ist:
build:
requires:
- setuptools
- wheel>=0.27
configparser
Ein INI-artiges Konfigurationsdatei, basierend auf dem, was configparser [9] akzeptiert, wurde in Betracht gezogen. Leider gibt es keine Spezifikation dafür, was configparser akzeptiert, was zu einer Ungleichmäßigkeit der Unterstützung zwischen den Versionen führt. Zum Beispiel ist das, was ConfigParser in Python 2.7 akzeptiert, nicht dasselbe wie das, was configparser in Python 3 akzeptiert. Während man das, was Python 3 akzeptiert, standardisieren und einfach den Backport des configparser-Moduls mitliefern könnte, bedeutet dies, dass dieses PEP kodifizieren müsste, dass der Backport von configparser von allen Projekten verwendet werden muss, die die von diesem PEP spezifizierten Metadaten konsumieren möchten. Dies ist übermäßig restriktiv und könnte zu Verwirrung führen, wenn jemand nicht weiß, dass eine bestimmte Version von configparser erwartet wird.
Eine Beispiel-INI-Datei ist:
[build]
requires =
setuptools
wheel>=0.27
Python-Literale
Jemand schlug vor, Python-Literale als Konfigurationsformat zu verwenden. Die Datei würde ein Dict auf der obersten Ebene enthalten, mit allen Daten innerhalb dieses Dicts, wobei Abschnitte durch die Schlüssel definiert sind. Alle Python-Programmierer wären an das Format gewöhnt, es gäbe implizit keine Drittanbieterabhängigkeit zum Lesen der Konfigurationsdaten, und es wäre sicher, wenn es von ast.literal_eval() [13] geparst würde. Python-Literale können identisch mit JSON sein, mit dem zusätzlichen Vorteil, dass sie nachgestellte Kommas und Kommentare unterstützen. Darüber hinaus könnte Pythons reicheres Datenmodell für zukünftige Konfigurationsanforderungen nützlich sein (z.B. Nicht-Zeichenketten-Dict-Schlüssel, Gleitkomma- vs. Ganzzahlwerte).
Auf der anderen Seite sind Python-Literale ein Python-spezifisches Format, und es wird erwartet, dass diese Daten möglicherweise von Packwerkzeugen usw. gelesen werden müssen, die nicht in Python geschrieben sind.
Eine Beispiel-Python-Literaldatei für die vorgeschlagenen Daten wäre:
# The build configuration
{"build": {"requires": ["setuptools",
"wheel>=0.27", # note the trailing comma
# "numpy>=1.10" # a commented out data line
]
# and here is an arbitrary comment.
}
}
Beibehaltung von setup.cfg
Es gibt zwei Probleme mit setup.cfg, das von setuptools als allgemeines Format verwendet wird. Erstens sind es .ini-Dateien, die Probleme haben, wie im Abschnitt configparser oben erwähnt. Das andere ist, dass das Schema für diese Datei nie streng definiert wurde und daher unbekannt ist, welches Format sicher für die Zukunft verwendet werden kann, ohne setuptools-Installationen möglicherweise zu verwirren.
Andere Dateinamen
Mehrere andere Dateinamen wurden in Betracht gezogen und abgelehnt (obwohl dies ein sehr starkes "Bikeshedding"-Thema ist und die Entscheidung hauptsächlich auf Geschmack beruht).
- pysettings.toml
- Die vernünftigste Alternative.
- pypa.toml
- Obwohl es sinnvoll ist, sich auf PyPA [11] zu beziehen, ist dies ein etwas nischiger Begriff. Es ist besser, wenn der Dateiname ohne domänenspezifisches Wissen Sinn ergibt.
- pybuild.toml
- Aus der restriktiven Perspektive dieses PEP ist dieser Dateiname sinnvoll, aber wenn jemals Nicht-Build-Metadaten zu der Datei hinzugefügt werden, hört der Name auf, sinnvoll zu sein.
- pip.toml
- Zu werkzeugspezifisch.
- meta.toml
- Zu generisch; ein Projekt möchte möglicherweise eine eigene Metadaten-Datei haben.
- setup.toml
- Obwohl es traditionell dank
setup.pyist, passt es nicht unbedingt zu dem, was die Datei in Zukunft enthalten könnte (z.B. ist die Kenntnis des Namens eines Projekts zwangsläufig Teil seines Setups?). - pymeta.toml
- Für Neulinge in der Programmierung und/oder Python nicht offensichtlich.
- pypackage.toml & pypackaging.toml
- Namensverwechslung dessen, was ein „Paket“ ist (Projekt versus Namensraum).
- pydevelop.toml
- Die Datei kann Details enthalten, die nicht speziell für die Entwicklung bestimmt sind.
- pysource.toml
- Nicht direkt mit Quellcode verwandt.
- pytools.toml
- Irreführend, da die Datei (derzeit) auf die Projektverwaltung ausgerichtet ist.
- dstufft.toml
- Zu personenspezifisch. ;)
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0518.rst
Zuletzt geändert: 2025-09-22 12:21:58 GMT