PEP 516 – Build-System-Abstraktion für pip/conda etc
- Autor:
- Robert Collins <rbtcollins at hp.com>, Nathaniel J. Smith <njs at pobox.com>
- BDFL-Delegate:
- Alyssa Coghlan <ncoghlan at gmail.com>
- Discussions-To:
- Distutils-SIG Liste
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Thema:
- Packaging
- Erstellt:
- 26-Okt-2015
- Resolution:
- Distutils-SIG Nachricht
Zusammenfassung
Dieses PEP spezifiziert eine programmatische Schnittstelle für pip [1] und andere Distributions- oder Installationswerkzeuge, die bei der Arbeit mit Python-Quellcode-Strukturen (sowohl Entwicklungsbäumen - z.B. dem Git-Baum - als auch Quellcode-Distributionen) verwendet werden soll.
Die programmatische Schnittstelle ermöglicht die Entkopplung von pip von seiner derzeitigen festen Abhängigkeit von setuptools [2], was aus zwei Hauptgründen wichtig ist:
- Sie ermöglicht neue Build-Systeme, die viel einfacher zu verwenden sein können, ohne dass sie auch nur den Anschein erwecken müssen, setuptools zu sein.
- Sie erleichtert es setuptools selbst, seine Benutzeroberfläche zu ändern, ohne pip zu beschädigen, was zu einer lockereren Kopplung führt.
Die Schnittstelle, die pip benötigt, um Build-Systeme installieren zu können, ermöglicht es pip auch, Build-Zeit-Anforderungen für Pakete zu installieren, was ein wichtiger Schritt ist, um pip die volle Funktionsparität mit den Installationskomponenten von easy-install zu ermöglichen.
Da PEP 426 ein Entwurf ist, können wir das von ihm definierte Metadatenformat nicht verwenden. PEP 427-Wheels sind jedoch weit verbreitet und recht gut spezifiziert, daher haben wir das METADATA-Format daraus für die Angabe von Distributionsabhängigkeiten und allgemeinen Projektmetadaten übernommen. PEP 508 bietet eine eigenständige Sprache zur Beschreibung einer Abhängigkeit, die wir in einem dünnen JSON-Schema kapseln, um Bootstrap-Abhängigkeiten zu beschreiben.
Da Python sdists, die in PEP 314 spezifiziert sind, ebenfalls Quellcode-Strukturen sind, aktualisiert dieses PEP die Definition von sdists.
Ablehnung der PEP
Der in diesem PEP vorgeschlagene CLI-basierte Ansatz wurde zugunsten des Python-API-basierten Ansatzes in PEP 517 abgelehnt. Der spezifische CLI, der zur Kommunikation mit Build-Backends in isolierten Unterprozessen verwendet wird, wird als Implementierungsdetail von Front-End-Entwicklerwerkzeug-Implementierungen betrachtet.
Motivation
Es gibt erhebliche aufgestaute Frustration im Python-Packaging-Ökosystem hinsichtlich des aktuellen Lock-ins zwischen Build-System und pip. Dieses Lock-in zu durchbrechen ist besser für pip, für setuptools und für andere Build-Systeme wie flit [3].
Spezifikation
Übersicht
Build-Werkzeuge werden durch Lesen einer Datei pypa.json aus dem Stammverzeichnis der Quellcode-Struktur gefunden. Diese Datei beschreibt, wie das Build-Werkzeug zu erhalten ist und den Namen des auszuführenden Befehls zur Invokation des Werkzeugs.
Alle Werkzeuge werden voraussichtlich eine einzelne Kommandozeilenschnittstelle einhalten, die nach dem vorhandenen Gebrauch von pip der setuptools setup.py-Schnittstelle modelliert ist.
pypa.json
Die Datei pypa.json fungiert als neutrale Konfigurationsdatei für pip und andere Werkzeuge, die Quellcode-Strukturen bauen möchten, um Konfigurationen abzurufen. Die Abwesenheit einer pypa.json-Datei in einer Python-Quellcode-Struktur impliziert ein setuptools- oder setuptools-kompatibles Build-System.
Das JSON hat das folgende Schema. Zusätzliche Schlüssel werden ignoriert, was die Verwendung von pypa.json als Konfigurationsdatei für andere verwandte Werkzeuge ermöglicht. Wenn dies geschieht, müssen die ausgewählten Schlüssel unter tools benannt werden.
{"tools": {"flit": ["Flits content here"]}}
- schema
- Die Version des Schemas. Dieses PEP definiert die Version „1“. Standardmäßig „1“, wenn abwesend. Alle Werkzeuge, die die Datei lesen, müssen bei einer nicht erkannten Schemaversion einen Fehler ausgeben.
- bootstrap_requires
- Optionale Liste von PEP 508-Abhängigkeitsspezifikationen, die installiert werden müssen, bevor das Build-Werkzeug ausgeführt wird. Zum Beispiel, wenn flit verwendet wird, könnten die Anforderungen sein:
bootstrap_requires: ["flit"]
- build_command
- Ein obligatorischer Schlüssel, dies ist eine Liste von Python-Formatstrings [8], die den auszuführenden Befehl beschreiben. Zum Beispiel, wenn flit verwendet wird, könnte der Build-Befehl sein:
build_command: ["flit"]
Wenn ein Befehl verwendet wird, der ein ausführbares Modul ist fred
build_command: ["{PYTHON}", "-m", "fred"]
Prozessschnittstelle
Der auszuführende Befehl wird durch einen einfachen Python-Formatstring [8] definiert.
Dies ermöglicht Build-Systeme mit dedizierten Skripten und solche, die über „python -m somemodule“ aufgerufen werden.
Prozesse werden mit dem aktuellen Arbeitsverzeichnis auf den Stamm der Quellcode-Struktur gesetzt ausgeführt.
Beim Ausführen sollten Prozesse nicht von stdin lesen - während pip derzeit Build-Systeme mit stdin, das mit seinem eigenen stdin verbunden ist, ausführt, werden stdout und stderr umgeleitet und es ist keine Kommunikation mit dem Benutzer möglich.
Wie üblich bei Prozessen zeigt ein nicht-nuller Exit-Status einen Fehler an.
Verfügbare Formatvariablen
- PYTHON
- Der verwendete Python-Interpreter. Dies ist wichtig, um Dinge aufrufen zu können, die nur Python-Einstiegspunkte sind.{PYTHON} -m foo
Verfügbare Umgebungsvariablen
Diese Variablen werden vom Aufrufer des Build-Systems gesetzt und sind immer verfügbar.
- PATH
- Der standardmäßige Systempfad.
- PYTHON
- Wie bei den Formatvariablen.
- PYTHONPATH
- Wird verwendet, um sys.path gemäß den normalen Python-Mechanismen zu steuern.
Unterbefehle
Es gibt eine Reihe separater Unterbefehle, die Build-Systeme unterstützen müssen. Die untenstehenden Beispiele verwenden für illustrative Zwecke einen build_command von flit.
- build_requires
- Abfrage der Build-Anforderungen. Build-Anforderungen werden als UTF-8-kodiertes JSON-Dokument mit einem Schlüssel
build_requireszurückgegeben, das aus einer Liste von PEP 508-Abhängigkeitsspezifikationen besteht. Zusätzliche Schlüssel müssen ignoriert werden. Der Befehl build_requires ist der einzige Befehl, der ohne Einrichtung einer Build-Umgebung ausgeführt wird.Beispiel-Befehl
flit build_requires
- metadata
- Abfrage der Projektmetadaten. Die Metadaten und nur die Metadaten sollten auf stdout in UTF-8-Kodierung ausgegeben werden. pip würde metadata nur einmal ausführen, um festzustellen, welche anderen Pakete heruntergeladen und installiert werden müssen. Die Metadaten werden als Wheel METADATA-Datei gemäß PEP 427 ausgegeben.
Beachten Sie, dass die vom metadata-Befehl generierten Metadaten und die in einem generierten Wheel vorhandenen Metadaten identisch sein müssen.
Beispiel-Befehl
flit metadata
- wheel -d OUTPUT_DIR
- Befehl zur Ausführung, um ein Wheel des Projekts zu erstellen. OUTPUT_DIR verweist auf ein vorhandenes Verzeichnis, in das das Wheel ausgegeben werden soll. Stdout und stderr haben keine semantische Bedeutung. Es sollte nur eine Datei ausgegeben werden – wenn mehr ausgegeben werden, wählt pip eine zufällige aus.
Beispiel-Befehl
flit wheel -d /tmp/pip-build_1234
- develop [–prefix PREFIX]
- Befehl zur Durchführung einer „Entwicklungsinstallation“ des Projekts vor Ort. Stdout und stderr haben keine semantische Bedeutung.
Nicht alle Build-Systeme können Entwicklungsinstallationen durchführen. Wenn ein Build-System keine Entwicklungsinstallationen durchführen kann, sollte es beim Ausführen einen Fehler ausgeben. Beachten Sie, dass dies dazu führt, dass Operationen wie
pip install -e foofehlschlagen.Die Option --prefix wird zur Definition eines alternativen Präfixes für die Installation verwendet. Während setuptools die Optionen
--rootund--userhat, können diese äquivalent über--prefixrealisiert werden, und pip oder andere Werkzeuge, die--rootoder--userOptionen akzeptieren, sollten dies entsprechend übersetzen.Die Option --root wird verwendet, um ein alternatives Stammverzeichnis zu definieren, innerhalb dessen der Befehl arbeiten soll.
Zum Beispiel
flit develop --root /tmp/ --prefix /usr/local
sollte Skripte innerhalb von
/tmp/usr/local/bininstallieren, auch wenn die verwendete Python-Umgebung berichtet, dass sys.prefix/usr/ist, was zur Verwendung von/tmp/usr/bin/führen würde. Ähnliche Logik gilt für Paketdateien usw.
Die Build-Umgebung
Mit Ausnahme des build_requires-Befehls werden alle Befehle innerhalb einer Build-Umgebung ausgeführt. Es ist keine spezifische Implementierung erforderlich, aber eine Build-Umgebung muss die folgenden Anforderungen erfüllen.
- Alle von den build_requires des Projekts spezifizierten Abhängigkeiten müssen von innerhalb von
$PYTHONimportierbar sein.
- Alle Kommandozeilenskripte, die von den build-required Paketen bereitgestellt werden, müssen im
$PATHvorhanden sein.
Eine Folgerung daraus ist, dass Build-Systeme keinen Zugriff auf Python-Pakete annehmen können, die nicht als build_requires oder in der Python-Standardbibliothek deklariert sind.
Hermetische Builds
Diese Spezifikation schreibt nicht vor, ob Builds hermetisch sein sollen oder nicht. Bestehende Build-Werkzeuge wie setuptools verwenden installierte Versionen von Build-Zeit-Anforderungen (z.B. setuptools_scm) und installieren nur andere Versionen bei Versionskonflikten oder fehlenden Abhängigkeiten. Es ist jedoch wahrscheinlich, dass eine bessere Konsistenz durch die ständige Isolierung von Builds und die ausschließliche Verwendung der spezifizierten Abhängigkeiten erzielt werden kann.
Es gibt jedoch nuancierte Probleme – zum Beispiel, wie Benutzer die Vermeidung einer fehlerhaften Version einer Build-Anforderung erzwingen können, die die Abhängigkeiten einiger Pakete erfüllt. Zukünftige PEPs können sich mit diesem Problem befassen, aber es ist derzeit nicht im Geltungsbereich – es beeinträchtigt nicht die Metadaten, die zur Koordination zwischen Build-Systemen und Dingen, die Builds durchführen müssen, erforderlich sind, und ist daher kein PEP-Material.
Upgrades
„pypa.json“ ist versioniert, um zukünftige Änderungen ohne Kompatibilitätsanforderungen zu ermöglichen.
Die Sequenz für das Upgrade eines der Schemata in einem neuen PEP wird lauten:
- Neues PEP ausgeben, das ein aktualisiertes Schema definiert. Wenn das Schema nicht vollständig abwärtskompatibel ist, muss eine neue Versionsnummer definiert werden.
- Konsumenten (z.B. pip) implementieren die Unterstützung für die neue Schemaversion.
- Paketautoren wählen das neue Schema, wenn sie bereit sind, eine Abhängigkeit von der Version von „pip“ (und potenziell anderen Konsumenten) einzuführen, die die Unterstützung für die neue Schemaversion eingeführt hat.
Der *gleiche* Prozess wird für die anfängliche Bereitstellung dieses PEP ablaufen: Die Verbreitung der Fähigkeit, dieses PEP ohne ein setuptools-Shim zu verwenden, wird größtenteils von der Akzeptanzrate der ersten Version von pip, die es unterstützt, abhängen.
Statische Metadaten in sdists
Dieses PEP befasst sich nicht mit der aktuellen Unfähigkeit, statische Metadaten in sdists zu vertrauen. Das ist ein separates Problem zur Identifizierung und Nutzung des Build-Systems, das in einer Quellcode-Struktur verwendet wird, unabhängig davon, ob es aus einem sdist oder nicht stammt.
Handhabung von Compiler-Optionen
Die Handhabung verschiedener Compiler-Optionen liegt außerhalb des Geltungsbereichs dieser Spezifikation.
pip behandelt Compiler-Optionen derzeit, indem es vom Benutzer bereitgestellte Zeichenfolgen an die Kommandozeile anhängt, die es beim Ausführen von setuptools verwendet. Dieser Ansatz reicht aus, um mit der in diesem PEP definierten Build-System-Schnittstelle zu arbeiten, mit der Ausnahme, dass global spezifizierte Optionen global nicht mehr funktionieren werden, da sich verschiedene Build-Systeme entwickeln. Dieses Problem kann in pip (oder conda oder anderen Installern) gelöst werden, ohne die Interoperabilität zu beeinträchtigen.
Langfristig sollten Wheels die Unterschiede zwischen Wheels, die mit einem Compiler oder Optionen erstellt wurden, ausdrücken können, und das ist PEP-Material.
Beispiele
Ein Beispiel für „pypa.json“ zur Verwendung von flit
{"bootstrap_requires": ["flit"],
"build_command": "flit"}
Wenn „pip“ dies liest, würde es eine Umgebung mit flit vorbereiten, bevor es versucht, flit zu verwenden.
Da flit heute keine setup-requires-Unterstützung hat, würde flit build_requires nur eine konstante Zeichenfolge ausgeben
{"build_requires": []}
flit metadata würde flit.ini abfragen und die Metadaten in eine Wheel METADATA-Datei umwandeln und diese auf stdout ausgeben.
flit wheel müsste einen Parameter -d akzeptieren, der ihm sagt, wo das Wheel ausgegeben werden soll (pip benötigt dies).
Abwärtskompatibilität
Ältere pips können weiterhin keine alternativen Build-Systeme verarbeiten. Das ist nicht schlimmer als der Status quo – und einzelne Build-Systemprojekte können entscheiden, ob sie einen setup.py-Shim aufnehmen oder nicht.
Alle bestehenden Build-Systeme, die Wheels produzieren und Entwicklungsinstallationen durchführen können, sollten unter dieser Abstraktion laufen können und benötigen nur einen spezifischen Adapter, der für sie erstellt und auf PyPI veröffentlicht wird.
In Abwesenheit einer pypa.json-Datei sollten Werkzeuge wie pip ein setuptools-Build-System annehmen und setuptools-Befehle direkt verwenden.
Netzwerkeffekte
Projekte, die Build-Systeme adoptieren, die nicht setuptools-kompatibel sind – d.h. die kein setup.py haben oder deren setup.py keine Befehle akzeptiert, die vorhandene Werkzeuge zu verwenden versuchen – werden von diesen vorhandenen Werkzeugen nicht installierbar sein.
Wo diese Projekte von anderen Projekten verwendet werden, wird sich dieser Effekt kaskadieren.
Insbesondere, da pip heute keine setup-requires unterstützt, wird jedes Projekt (A), das ein setuptools-inkompatibles Build-System adoptiert und als setup-requirement von einem zweiten Projekt (B) konsumiert wird, das selbst noch kein pypa.json übernommen hat, B uninstallierbar machen. Das liegt daran, dass setup.py in B easy-install auslösen wird, wenn „setup.py egg_info“ von pip ausgeführt wird, und dies versuchen wird, A zu installieren und fehlschlagen wird.
Daher empfehlen wir, dass Werkzeuge, die derzeit als setup-requirement verwendet werden, entweder sicherstellen, dass sie einen setuptools-Shim beibehalten oder ihre Konsumenten finden und diese alle dazu bringen, die Verwendung einer pypa.json zu aktualisieren, bevor sie sich selbst bewegen. Pragmatisch ist das unmöglich, daher ist der Rat, einen setuptools-Shim auf unbestimmte Zeit beizubehalten – sowohl für Projekte wie pbr, setuptools_scm als auch für Projekte wie numpy.
setuptools-Shim
Es wäre möglich, einen generischen setuptools-Shim zu schreiben, der wie setup.py aussieht und im Hintergrund pypa.json verwendet, um die Builds anzutreiben. Dies ist für pip nicht erforderlich, um das System zu nutzen, würde aber Paketautoren ermöglichen, die neuen Funktionen zu nutzen und gleichzeitig die Kompatibilität mit älteren pip-Versionen zu erhalten.
Begründung
Dieses PEP begann mit einem langen Mailinglisten-Thread auf distutils-sig [6]. Anschließend wurde ein Online-Meeting abgehalten, um alle Positionen der Beteiligten zu debuggen. Die Protokolle davon wurden auf die Liste gestellt [7].
Diese Spezifikation ist eine Übersetzung des dort erreichten Konsenses in PEP-Form, zusammen mit einigen willkürlichen Entscheidungen zu den verbleibenden geringfügigen Fragen.
Das grundlegende Heuristik für das Design war, sich auf die Einführung einer Abstraktion zu konzentrieren, ohne Entwicklungsarbeit zu erfordern, die nicht streng an die Abstraktion gebunden ist. Wo die Lücke klein zu Verbesserungen ist oder die Kosten für die Verwendung der bestehenden Schnittstelle sehr hoch sind, haben wir die Verbesserung als Abhängigkeit übernommen, andernfalls wurden solche auf zukünftige Iterationen verschoben.
Wir haben Wheel METADATA-Dateien anstelle der Definition einer neuen Spezifikation gewählt, da pip bereits Wheel .dist-info-Verzeichnisse verarbeiten kann, die alle notwendigen Daten in einer METADATA-Datei kodieren. PEP 426 kann nicht verwendet werden, da es sich noch im Entwurf befindet, und die Definition eines neuen Metadatenformats, obwohl wir dies tun sollten, ist ein separates Problem. Die Verwendung eines Verzeichnisses auf der Festplatte würde der Schnittstelle keinen Mehrwert hinzufügen (pip muss dies heute aufgrund von Einschränkungen in der setuptools CLI tun).
Die Verwendung von „develop“ als Befehl liegt daran, dass es kein PEP gibt, das die Interoperabilität von Dingen spezifiziert, die das tun, was „setuptools develop“ tut – daher müssen wir das definieren, bevor pip die Verantwortung für die „develop“-Schritt übernehmen kann. Sobald dies geschehen ist, können wir ein Nachfolge-PEP zu diesem herausgeben.
Die Verwendung einer Kommandozeilen-API anstelle einer Python-API ist etwas umstritten. Grundsätzlich kann alles zum Laufen gebracht werden, und die pip-Maintainer haben sich stark für die Beibehaltung einer prozessbasierten Schnittstelle ausgesprochen – etwas, das in pip heute ausgereift und robust ist.
Die Wahl von JSON als Dateiformat ist ein Kompromiss zwischen mehreren Einschränkungen. Erstens gibt es keinen stdlib YAML-Interpreter, noch für eines der anderen strukturierten Dateiformate mit geringer Reibung. Zweitens ist INIParser aus einer Reihe von Gründen ein schlechtes Format, hauptsächlich weil es nur minimale Struktur hat – aber pip-Maintainer mögen es nicht. JSON ist im stdlib, hat genügend Struktur, um alles einzubetten, was wir in Zukunft wollen, ohne eingebettete DSLs zu benötigen.
Donald schlug vor, setup.cfg und die bestehende setuptools-Kommandozeile zu verwenden, anstatt etwas Neues zu erfinden. Während dies die Interoperabilität mit weniger sichtbaren Änderungen ermöglichen würde, erfordert es fast ebenso viel Engineering auf der pip-Seite – Suche nach dem neuen Schlüssel in setup.cfg, Implementierung der nicht installierten Umgebungen, um den Build auszuführen. Und der Wunsch anderer Build-Systemautoren, ihre Benutzer nicht mit etwas zu verwirren, das wie setuptools aussieht, aber sich ganz anders verhält, scheint ein größeres Problem zu sein, als dass pip lernt, ein benutzerdefiniertes Build-Werkzeug aufzurufen.
Die Metadaten- und Wheel-Befehle müssen konsistente Metadaten haben, um eine Race-Condition zu vermeiden, die sonst auftreten könnte, wenn pip die Metadaten liest, darauf reagiert und dann das resultierende Wheel inkompatible Anforderungen hat. Diese Race wird heute von Paketen mit PEP 426-Umgebungsmarkern ausgenutzt, um mit älteren pip-Versionen zu arbeiten, die keine Umgebungsmarker unterstützen. Dieser Exploit ist mit diesem PEP nicht erforderlich, da entweder der setuptools-Shim verwendet wird (mit älteren pip-Versionen) oder eine pip mit Umgebungsmarkern in Gebrauch ist. Der setuptools-Shim kann die Ausnutzung des Unterschieds, den ältere pip-Versionen benötigen, übernehmen.
Wir diskutierten über die Aufnahme eines sdist-Verbs. Der Hauptgrund dafür war sicherzustellen, dass Build-Systeme sdists produzieren können, die pip bauen kann – aber das ist zirkulär: Der gesamte Sinn dieses PEP ist es, pip zu ermöglichen, solche sdists oder VCS-Quellcode-Strukturen zuverlässig und ohne Implementierung von setuptools zu konsumieren. Die Fähigkeit, neue sdists aus bestehenden Quellcode-Strukturen zu erstellen, ist nichts, was pip heute tut, und obwohl es einen PR gibt, um dies als Teil des Bauens aus dem Quellcode zu tun, ist er umstritten und es fehlt an Konsens. Anstatt allen Build-Systemen eine Anforderung aufzuerlegen, behandeln wir es als YAGNI und werden ein solches Verb in einer zukünftigen Version der Schnittstelle hinzufügen, falls erforderlich. Die bestehenden PEP 314-Anforderungen für sdists gelten weiterhin, und distutils- oder setuptools-Benutzer können setup.py sdist verwenden, um einen sdist zu erstellen. Andere Werkzeuge sollten sdists erstellen, die mit PEP 314 kompatibel sind. Beachten Sie, dass pip selbst keine PEP 314-Kompatibilität benötigt – es verwendet keine der Metadaten von sdists – sie werden wie Quellcode-Strukturen von der Festplatte oder Versionskontrolle behandelt.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0516.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT